Javascript in Laravel (Vue single file components)

Introducing Vue single file components

Pim Hooghiemstra
PLint-sites
7 min readMay 14, 2018

--

Update May 2021

Please note that this post is currently 3 years old. As you know, things move fast in our tech world and event though the principles described in this post are still valid, there might be better ways to achieve the same result; You are free to continue reading, but I recently wrote a post about our current approach.

Additionally, Laravel 8 brings a new starter kit to quickly scaffold the frontend of your next project. It’s called Laravel Jetstream and is definitely worth taking a look. I’ll choose Laravel Jetstream with Inertia scaffolding for my next project.

Therefore, I am removing the code repository that originally came with this post.

Original post

This is the fifth post in our Javascript in Laravel series. If you are interested in the other parts of the series have a look at the overview post.

In the last post, we found out that using a global Vue instance with data and methods could quickly lead to an unmaintainable codebase. Hence, we abandon that approach. Instead we will apply the ‘single Vue instance for entire application’ approach and utilise Vue single file components.

…and another single tree

Outline of the approach

The single Vue instance for the entire application works like this

  • Import all components we need for the various pages in app.js
  • Instantiate the Vue instance passing all components in the components section of the Vue instance
  • Load the compiled app.js in the main layout file.

In a nutshell the code ( app.js) looks like this

import Component1 from './components/component1'
import Component2 from './components/component2'
import Component3 from './components/component3'
import Component4 from './components/component4'
const app = new Vue({
el: '#app',
components: {Component1, Component2, Component3, Component4}
})

As before, the compiled app.js is loaded at the bottom of the main layout file app.blade.php and we have to make sure that the @yield('content') is wrapped in a div with id app to make it work.

We are still working on the same registration form and like to apply some Javascript behaviour to improve the usability. Instead of adding Vue directives to native HTML elements as we did in the last post, we have created a single file component that we can use directly in the blade view. Have a look at the new form.blade.php:

<div class="container-block">
<div class="container">
<div class="row">
<div class="col-md-6">
<my-bootstrap-vue-form></my-bootstrap-vue-form>
</div>
</div>
</div>
</div>

Compared to the previous version, the number of lines has decreased drastically. However, these lines are only relocated from the blade view to the Vue component as we are about to see.

Well, a rather long component with 248 lines of code. The template part consists mainly of Bootstrap Vue components (recognizable by the <b-…> syntax. Bootstrap Vue is basically the Bootstrap framework (version 4) made available in Vue. Hence, instead of writing all the form HTML in the blade view, we have to do it in Vue. What a win after all :-)

Let’s discuss some parts of the code:

  • In the data object we have an object form to hold all data for the v-model part of the various inputs (l. 121).
  • Bootstrap-Vue comes with very nice markup for validation of the inputs. The typical Bootstrap messages are automatically displayed if we use the right props. For example, to display an error message we need the invalid-feedback prop on the form-group combined with a state prop on the form-input (l. 13, 20).
  • The input field for the name of the user is conditionally shown, based on the value of the gender. So instead of inspecting the DOM, in the Vue world everything is based on the data, the single source of truth (l. 26–35).
  • The email address is validated on the server once blurred (l. 21). Again, the invalid-feedback, valid-feedback and the state are used here to display the messages.
  • Blurring the zipcode field or changing the country dropdown triggers the checkZipcode method (l. 201) which checks whether the combination of the two fields is valid. If not, the state of the two fields is set to false and the error messages are displayed. Note that we could have made these messages a little smarter by informing the user about the required format of the zipcode.
  • A special note for the use of nextTick in the checkZipcode method. Somehow, the country data is not yet changed when we enter this method. Therefore we need to wait for the nextTick, a core Vue function. Why this happens is not entirely clear to me at this point.

Although the code is in this Vue component is rather long, we have everything that has to do with the form in one file: the template and the behaviour. You might even add special styling by including a style block.

In my opinion, the biggest advantage of these single file components are the maintainability, the single responsibility principle (this component is responsible for the form and that’s all) and finally reusability!

Importing the component in app.js

We store the MyBootstrapVueForm component in the components folder resources/assets/js/components and import it like so in app.js:

// app.js
require('./bootstrap');
import MyBootstrapVueForm from './components/MyBootstrapVueForm';const app = new Vue({
el: '#app',
components: {MyBootstrapVueForm}
});

What about other pages of the app?

So far we only discussed this single page containing the registration form. What about a more complex application with Javascript in other pages? Or multiple blocks that need Vue on the page?

That is actually quite simple in this approach. For every block or page that needs Javascript behaviour, we just write a Vue component that contains all HTML and behaviour we need and that’s about it! This way we can combine Laravel’s blade views with enhanced HTML blocks that are powered by Vue.

As an example, suppose we have the registration form on /register and we decide to add a profile page. Then we would need a form to specify our preferences, for example: favourite holiday country, our birthday and our favourite color.

In Vue, we would just create another component and create a form, just like before. However, a datepicker and colorpicker component are not available in Bootstrap-Vue. However, of course they are available on npm. Below you’ll see the form in action

A second form on the profile page

However, it only gets fun when you open up the colorpicker or datepicker:

Hence, we create a second single file component, MyOtherForm.vue. Including it in the page is as simple as

  • Import the component in app.js
  • Use it in the blade view <my-other-form></my-other-form>

Here is app.js after the import:

// app.js
require('./bootstrap');
import MyBootstrapVueForm from './components/MyBootstrapVueForm';
import MyOtherForm from './components/MyOtherForm';
const app = new Vue({
el: '#app',
components: {MyBootstrapVueForm, MyOtherForm}
});

The code for the form itself is also rather simple, because I left out validation for simplicity.

The template of the component is rather self explanatory, it follows the Bootstrap-Vue setup discussed before. The Swatches component (the colorpicker) and the Datepicker are imported and easy to use.

Note that we had to install these modules

npm install --save vuejs-datepicker vue-swatches

Recipe to start with Vue

Due to all the explanation going on in this series the exact steps to start with Vue in a Laravel project might be difficult to spot. Therefore, I wrote down a recipe to generate the Example Component (discussed in the install Vue post).

I assume you have a fresh Laravel application installed and have node and npm installed globally on your machine.

# install dependencies from package.json
npm install
# compile the Vue code
npm run dev
# add the component to the welcome blade view
# inside the div with id app
<example-component></example-component>

Concluding remarks

When Vue is used in a Laravel project it is essential to think about the behaviour you want to add to a page or a form.

In contrast to using jQuery where we can manipulate the DOM on the fly, using Vue requires a bit more thinking first.

However, once you have decided to use Vue to setup a form, writing the logic for the form (including validation) becomes much easier compared to the jQuery approach. This is due to the data driven approach Vue requires you to use: user interaction triggers methods that change the data in your Vue component, which will automatically rerender.

I acknowledge the configuration work you have to do when setting up a project with Vue: installing the modules, setting up the entry point file for Webpack compilation and so on. However, it is definitely worth the effort once your app becomes larger and more complex!

Note that the bundled app.js steadily grows when the app becomes more complex, and all components are loaded on every page, even if that page does not use the components. Not ideal in terms of performance. Code splitting may be used to improve here but is outside the scope of this post.

A final note: often we like to quickly scaffold a project using a (Bootstrap) theme. Typically these themes are not written in Vue and combining the two can be a challenge. We recently challenged ourselves and wrote about it in the final post of this series.

--

--

Pim Hooghiemstra
PLint-sites

Love to build Laravel + Vue applications! Founder of PLint-sites. https://plint-sites.nl