vincej's avatar
Level 15

Advice on how best to add *multiple* Vue pages into a Laravel project

I am evaluating LiveWire vs Vue. I find the Vue docs and tutorials excellent, except in one area: How to add several Vue pages into a Laravel project. Essentially I have thousands of lines of JS/JQuery but I am thinking I should catch up with the times. So, going forward, I do not want to create a true Vue MPA, just substitute Vue where needed, instead of the JS/JQuery. I have spent the morning Googling including the Vue docs on configuring pages as well as the Vue community. I get the feeling that not many people do this.

Can anyone point me towards any written or video resources on how best to do this?

Many thanks!

0 likes
22 replies
vincej's avatar
Level 15

@jlrdw Thanks for that. I had a little look this AM and did not see much on creating an MPA. My gut feeling is that LiveWire would be more suitable, but Calebs docs are far from great.

Question: do you use LiveWire or Vue?

martinbean's avatar

@vincej The main difference between writing vanilla JavaScript/jQuery and Vue, is that Vue is used for self-contained components instead of having a JavaScript file with however many lines that is then doing lots of explicit manipulation of the DOM. So you can wrap common UI fragments in Vue components that will have they’re own markup and JavaScript and run in isolation.

Livewire is OK for building dynamic UIs but I still prefer JavaScript/Vue for the instant feedback. Livewire does AJAX round-trips to update your UI. This can be a few hundred milliseconds depending on network conditions and a noticeable lag. So for simple things like checking a checkbox shows a new section on your page, you can check the checkbox and they’ll be a noticeable delay before the Livewire response comes back with the new HTML and injects it into your page. Whereas with a JavaScript-based approach it’s instant, you can show loading/progress indicators whilst waiting for results, every interaction doesn’t require a server round-trip and re-rendering, etc.

vincej's avatar
Level 15

@martinbean Thanks for that. I have looked at some of your other posts on the topic. I have played with both frameworks. I wish LiveWire had the docs and tutorials of Vue. AS a solo developer time is my single most valuable asset. I find that the LW learning curve is made steeper by simple fact that there is not the abundance of materials. I find myself endlessly hunting for simple tasks./ Vue on the other hand is more technically complicated but has great materials. Cheers!

martinbean's avatar

@vincej Yeah. I think the issue there is, Livewire is a lot less mature than say, Vue, so there’s going to be less documentation and resources on it at this point in time. It’s also essentially a new paradigm, so there isn’t really anything comparable unlike Vue: Vue is a component library so if you learn the fundamentals of Vue you can apply that understanding to other component libraries like React. If you learn Livewire then… you’ve learned Livewire and don’t really have a lot of knowledge that can be re-applied to anything else.

piljac1's avatar

By adding several pages, what do you mean exactly? Paginate a list? Handle Vue page component changes in JavaScript (without a full page refresh)? Switch between different Vue page components with a full page refresh using Blade views?

vincej's avatar
Level 15

@piljac1 Hi thanks for helping out,

I have a multitude of blade views. Some of those blade views pull in data from the DB, and then that data is manipulated using JS/JQuery. For example, I pull in products and prices and then run a whole bunch of JS manipulating that. The results are then saved. There are a multiple page views which require this JS/JQuery, but not all. I have hundreds, maybe thousand of lines of JS/JQuery. There is a lot of computation going on. Going forward I was thinking it would be nice to apply a cleaner and more modern approach. Thanks!

jlrdw's avatar

@vincej one technique I use is server fetched partials, see https://laracasts.com/series/javascript-techniques-for-server-side-developers/episodes/1

However if pagination is needed, I just use a iframe. It is nothing using fetch js to have the bookkeeper load the checking account using the above technique, except to have the full laravel pagination I use the iframe.

But even without an iframe, it is easy to write next and previous links.

Either way, at least view that video.

Edit:

I have experimented with both vue and livewire. Just my opinion here, but I would go with vue. Just me, but I did not like livewire. But I now use fetch js and pretty well like the server fetched partials. Sometime in a modal sometimes not.

vincej's avatar
Level 15

@jlrdw Thanks for that recommendation, I am not familiar with that video; I will check it out..

I am drawn towards Vue for the simple fact that it has excellent documents, and videos. However, my #1 Vue issue which so far no one has advised on is how to deliver multiple Vue pages mixed in with blade views ie build hybrid system of Blade and Vue pages.

vincej's avatar
Level 15

@jlrdw I watched through the video you sent. It was interesting. Thanks.

Currently I am seeking how to replace my JS / JQuery which is in many parts highly computational. I wish I could find some resources on how to add multiple Vue pages into a hybrid Laravel / Vue system. Livewire should be able to do it as the computations can be handled inside Laravel, but sadly I am struggling to get the answers out of the docs which I find are a bit vague. Cheers.

drehimself's avatar

Solid advice so far, but I'm also going to throw in another recommendation for replacing your JS/JQuery code: Alpine.js.

If the majority of your existing JS/JQuery code is quite simple and does things like toggling UI elements, I think Alpine is worth considering. It's extremely lightweight and does not require a build step: just plop in the CDN and it's ready to use.

However, if your JS code does a lot of AJAX requests and communication with Laravel/backend data, then yes, I think Livewire or Vue is the better option.

vincej's avatar
Level 15

@drehimself Hi there! I have watched several of your videos! Great job. From what I have read Alpine will not cut it. My JS is highly computational with loads of AJAX calls.

However, if you can advise on how to build a Laravel app with multiple Vue pages mixed in as a hybrid MPA I would LOVE to hear it. I have not managed to find any real advice on it's viability. Thanks!

martinbean's avatar

@vincej You don’t have “Vue pages”. You would have components with Vue. Components would wrap up common UI patterns in your application. You register these components in an “entry” file (just a JS file). When you include this JS file in your Blade view, you can then reference and use any of the registered components in your Blade views.

vincej's avatar
Level 15

@martinbean Ok, I get your point. Well made. I guess I have gotten wrapped up in the whole SPA vs MPA question. I have played with Vue a bit and I understand how a Blade template includes a component made up of Blade and Vue code. So - if I understand you correctly, I could have as many such Blade templates as I wish and I can simply add the differing components to each page as necessary. Moreover I do not have to worry about routing as Laravel continues to manage that. Have I got that right?

martinbean's avatar

@vincej Yeah, that’s right :)

When you create components in Vue, just think of them as self-contained snippets of HTML with interactivity. So instead of having plain ol’ HTML and then a JavaScript file that does DOM manipulations based on IDs or other selectors, you’d have a component that wraps up the behaviour of a particular piece of your UI and is self-contained and isolated.

It’s hard to come up with applicable examples without seeing any of your code. One example could be a “stepper” input, where you have a numeric input but then also buttons either side to decrement and increment the value. It looks pretty simple on the surface, but there’s a lot to consider:

  • Minimum and maximum values
  • Decrement button should be disabled if value reaches minimum value
  • Increment button should be disabled if value reaches maximum value

So you could wrap this up in a Vue component. Now, any time you want to display this stepper widget, you can do so by just referencing a Vue component you’ve made for it:

<!-- resources/views/some/blade/view.blade.php -->

<!--
  Lots of HTML mark-up
-->

<!-- Render a Vue component within Blade template -->
<stepper-input min="0" max="50" />

<!--
  Lots more HTML mark-up
-->

So the stepper-input component would be contain the mark-up for the stepper component, and the functionality for that stepper component. The min and max attributes are “props”, values passed to the component that the component will need for initialising itself.

vincej's avatar
Level 15

@martinbean I can not thank you enough for helping out like this. It is not taken for granted. Yes, I am familiar with the code example you give, it is one of the early tutorials on LiveWire.

Question 1: Thanks to some code liberated from Povilas Korop, one of my projects already already uses Vue. So he declares his component like this:

Vue.component('front-page', require('./components/Front.vue').default);

So, if I am doing multiple Blade views with Vuejs, do I just create another

Vue.component('New-page', require('./components/NewPage.vue').default);

Question 2 You commented :

It’s hard to come up with applicable examples without seeing any of your code.

Ok - so here is some typical code from my "main" project. I have hundreds (thousands?) of lines of such code. One motivation to moving to Vue or LiveWire is that it is difficult to maintain years after having written it. I am not going to rewrite this stuff, but this project is a living and expanding thing, so I don't want to add to this. My only concern with Vue is it appears to be technically much more complex than LW BUT, the docs are superior. So, considering this type of code, in your opinion, would I be better off with LiveWire or Vuejs? :

  $('#template_name').on('click', function () {
        $('#quotation').show();
        $("#projectQuotation").empty();
        var id = $('#template_name').val();
        var product = '';
        var product_id = '';
        var quantity = '';
        var cost = '';
        var price = '';

        $.ajax({
            url: '/get_template/' + id,
            dataType: "json",
            type: "GET",
            success: function (data) {
                data.forEach(function (item)    // this takes the array an iterate over it delivering the specific name of the subcategory.
                {
                    product = item.product_name;
                    product_id = item.product_id;
                    quantity = item.quantity;
                    cost = item.line_cost;
                    price = item.price;
                    // table body
                    $("#projectQuotation").append("<tr class='delete_row, .projectTemplate'>" +
                        "<td><input type='text' name='product[]' class='form-control  product_done clear_values' readonly value='" + product + "'></td>" +
                        "<td><input type='text' name='product_id[]' class='form-control  product_iddone  clear_values' readonly value='" + product_id + "'></td>" +
                        "<td><input type='text' name='price_done[] 'class='form-control pricedone  clear_values' readonly value='" + price + "'></td>" +
                        "<td><input type='text' name='quantity[]' class='form-control  quantity clear_values' value='" + quantity + "'>" +
                        "</td> <td><input type='text' name='cost[]' class='form-control  cost clear_values' readonly value='" + cost + "'></td>" +
                        "<input type='hidden' name='cost_hidden[]' class='form-control line_cost_hidden clear_values' readonly value='" + cost + "'>" +
                        "<td style=\"text-align: center\" ><input class='projectQuotationCheckbox'  type='checkbox' name='delete' value='true'></td>"+    //class='clear_values'
                        "</tr>");
               });


                var subTotal = 0;
                $("input.line_cost_hidden").each(function () {
                    var lc = $(this).val();
                    var inputCost = Number(lc);
                    template_subTotal += inputCost;
                    var displayCost = $.formatNumber(template_subTotal, {format: "#,###.00", locale: "ca"});
                    $('#subtotal').val(displayCost);

                    var discount_percent = $("#discount").val();
                    if (discount_percent == "") {
                        discount_percent = 0;
                    }
                    var calculations = calculateDiscount(template_subTotal, discount_percent);  //newCalculate is a separate function and recalculates the totals //
                    setValues(calculations, $("#updateValues"));
                });
            }
        }); //END AJAX
    });


martinbean's avatar

@vincej No problem! Happy to help and try and share any knowledge I might have on the topic as we all start from the same place—I remember transitioning from writing jQuery-heavy scripts to writing more and more Vue 🙂

Yeah, looking over that code snippet it looks like you’re selecting a template of sorts, making an AJAX call, and then updating the DOM based on the results coming back from that AJAX request. I think I’d need to see more of the surrounding elements to see how this is working in situ, but at its simplest it seems to be appending a row to a table based on clicking something else.

<template>
    <div>
        <a href="#" role="button" @click="onTemplateSelect(1)">Add new X-type product</a>
        <a href="#" role="button" @click="onTemplateSelect(2)">Add new Y-type product</a>
    </div>
    <table>
        <thead>
            <tr>
                <!-- Put <th> elements here -->
            </tr>
        </thead>
        <tbody>
            <tr v-for="(item, index) in items" :key="index">
                <td><input type="text" name="product[]" :value="item.product_name" class="form-control" readonly></td>
                <td><input type="text" name="product_id[]" :value="item.product_id" class="form-control" readonly></td>
                <td><input type="text" name="price_done[]" :value="item.price" class="form-control" readonly></td>
                <td><input type="text" name="quantity[]" :value="item.quantity" class="form-control" readonly></td>
                <td><input type="text" name="cost[]" :value="item.line_cost" class="form-control" readonly></td>
            </tr>
        </tbody>
    </table>
</template>

<script>
export default {
    data() {
        return {
            items: [],
        };
    },
    computed: {
        total() {
            return this.items.reduce((total, item) => {
                let subtotal = item.cost - this.calculateDiscount(item.cost);

                return total + subtotal;
            }, 0);
        },
        totalFormatted() {
            return new Intl.NumberFormat('en', {
                currency: 'CAD',
                style: 'currency',
            }).format(this.total);
        },
    },
    methods: {
        calculateDiscount(amount) {
            // FIXME: Get discount percentage from somewhere
            let discountPercentage = 0;
            let discountAmount = amount * discountPercentage;

            return amount - discountAmount;
        },
        onTemplateSelect(id) {
            axios.get(`/get_template/${id}`).then((response) => {
                response.data.items.forEach((item) => {
                    this.items.push(item);
                });
            });
        },
    },
};
</script>

What the main different you should see is, there’s no manually manipulating the DOM based on IDs or other selectors. Interaction is done via state changes rather than, “I’m going to write some code that adds these nodes, or removes these nodes, or changes the inner text/HTML of these nodes”.

So, I’ll try and give some highlights of what the above is doing:

  • The above is an example of a “single-file component” (SFC for short). These are components that have both the template (HTML) and the functionality (the script part). So you can almost think of it like MVC where the view is separated from the business logic.
  • The table body might look like it has a single <tr>, but you’ll see that tr has a v-for directive. This is iterating over items, so if you have ten elements in your items array then ten <tr> elements will be shown. This is also interactive meaning if you add or remove an element from that array, the UI will update accordingly. You don’t need to manually append rows or remove rows from the HTML.
  • You’ll see some HTML attributes are prefixed with a colon. This is dynamically setting the value based on a JavaScript expression. So :value="item.product_name" isn’t setting the value attribute to the literal string item.product_name, but rather the value of that JavaScript expression.
  • Inside the <script> tag you’ll see it’s just returning an object. That’s essentially what Vue components are: JavaScript objects with specific elements. The data element returns an object that’s the initial state of your component. So I set the initial state for the items array to just be an empty array. The different between data and props is, data is state that you expect to change in your component, whereas props are for passing any initial values the components need (think of props as like arguments in a class constructor, and then data is the class properties you would set and change within the class).
  • There are then some computed properties. These are properties you can access within your template, but their values will be evaluated on demand. So, when you make your AJAX request and then re-calculate totals based on the data you got back, you instead just have this single computed total property that will automatically keep its value up to date based on the state of your component, and you can reference this computed property in your template (i.e. <p>Total: {{ total }}</p>).
  • Finally, you have some methods. These are just what they say on the tin: generic methods that you can call in your component. So, I created a onTemplateSelect method that’s called when one of the buttons at the top of the template is clicked. Again, there’s no need for specifying IDs and setting up event listeners here; Vue is declarative so I can add that weird looking @​click directive to say, “When this element is clicked, call this method with this parameter”.
  • Finally, there’s a calculateDiscount method. I couldn’t see from your example how the discount percentage was seeded, but this would simply take the un-discounted amount as a parameter and return the newly-discounted amount. You can see me use this method in the totalFormatted computed property where I apply it to the total. You could echo this amountFormatted computed property somewhere in your template (i.e. <p>Total: {{ amountFormatted }}</p>) and it will display the total formatted as a currency value (i.e. CA$23.50).

Hopefully this gives you some insight in how to start re-factoring from jQuery to Vue components. I’d say this would be a big one to do as a first pass though, and maybe look for some interactions that are maybe smaller and would be able to be wrapped up in more “discreet” Vue components. But yeah, happy to answer any other questions you may have with Vue and moving from jQuery to Vue components 🙂

vincej's avatar
Level 15

@martinbean WOW! I can't believe you went to all this effort! You are truly a rockstar!!! Thank you is not enough! With an expression like "These are just what they say on the tin", you must have some British influence. I spent 33 years living in UK, from age 12 - 45!

I have loads of JS/JQuery just like this, and I have no intention of rewriting it. It would take another lifetime. However going forward I don't want to add to this pile and so I am looking at LiveWire and Vue. I spent much of yesterday afternoon evaluating LiveWire attempting to do something very, very easy in JQuery; selecting a specific value from a specific text input wrapped in a td, in a large data table. The data table having been generated by a foreach. I had three people contributing to the discussion. In the end we all gave up. Bottom line, I need something which can deliver the same capability as JS/JQuery without heeps of pain. Sure everything is a learning curve, but as a solo developer, for me, time is my most expensive asset. I have higher hopes for Vue than for LW. Essentially I feel LW is suited to those people with weak JS skills.

So, last thing, and hopefully I won't bug you much anymore. If I have a component in my Laravel app, like this:

Vue.component('front-page', require('./components/Front.vue').default);

Can I create a new component in whole new Blade view like this?

Vue.component('New-page', require('./components/New.vue').default);

I conclude, again with a giant thank you for all your work. You are a credit to the forum.

Cheers Vince ( Calgary / Alberta / Canada )

martinbean's avatar

With an expression like "These are just what they say on the tin", you must have some British influence.

@vincej Ha-ha, yeah 😄 I live in Newcastle, in the North-East of England.

So, last thing, and hopefully I won't bug you much anymore. If I have a component in my Laravel app, like this

I think looking at that code, maybe try and get away from thinking you need to create whole pages in Vue. Instead, think of Vue for creating components for chunks, or portions, of a single page. It’s entirely possible to use the same Vue component in multiple different Blade views. For example, a like button component, where you’d want to re-use the same component but for many different models (like a blog post, like a comment, like my reply to your forum thread, etc).

Whilst it is entirely possible to create whole pages with Vue, it seems you’re like me and very much just want to use Vue for progressively-enhancing existing, Blade-rendered pages rather than going to the whole hog of, “My page is JavaScript”. So you could register multiple components and use any number of them in a single Blade template.

vincej's avatar
Level 15

@martinbean Newcastle - I have been there many times. I used to live just outside London, in Hertfordshire. I have been in IT all my life albeit on the project management / consultancy side, and used to work around Bracknell.

Nope I do not want to create whole pages with Vue, just the JS bits. Sorry to keep drilling on on this, can I create multiple components on different pages just like this?

Vue.component('New-page', require('./components/New.vue').default);

Many thanks!

martinbean's avatar

@vincej Yeah, there’s no intrinsic link between components and pages in your website. You can use multiple components on a single page, and you can use a single Vue component on multiple pages.

mariohbrino's avatar

You can use inline templates and or lazy loading components.

I made a project on vuejs 2 with tailwindcss and laravel to handle pagination that has multiple components.

mazejs - example

Please or to participate in this conversation.