Laravel and Vue JS: Advanced Frontend Webapp Architecture
By: Russ Weas
Tags: Laravel, JavaScript, Vuejs, Tutorial

Part 1: using this.app in each view

Quick Note: In coffeescript, saying @var and this.var results in the same code. I prefer the first way, but you don’t have to.

First, let’s go to the main HTML file that contains the <component is=“{{ currentView}}”>. We need to add the app as a prop to that. So, change that to <component is=“{{currentView}}” app=“{{@ app }}”></component> .

If you’ve used Vue before you’ll be familiar with the mustache syntax, but the {{@ might leave you a little confused. Basically, it says pass the app into the component, but if the component makes a change to the app, reflect it in the scope that contains the component too. In other words it enforces two-way binding. If you didn’t have the @, you could do something to the app in the view and then nothing would happen anywhere else!

Of course, if you are in a .blade.php file, change that to <component is=“@{{currentView}}” app=“@{{@ app }}”></component> so Laravel doesn’t get confused and try to look for a currentView and app constant.

Also, in order to make things run better, you may want to add keep-alive to the end of the <component> tag. It basically keeps the current view alive in the background instead of deleting it, so if you change currentView back to it Vue doesn’t have to do more work. However, if you want to get updated data anytime the user changes views, it would be very easy to just remove keep-alive so that the ready method is recalled on the component.

Next, we need to make sure that app is added a valid props array for each view. In the module.exports array, go add an array called props if it does not already exist. Next, add a string called ‘app’ to it. Here’s an example:

views/analytics.js

Finally, we need to configure our app.js file to have an app variable that points to itself. Add app: {} to its data object. Then, add a created method to the app. In it, say this.app = this; . You’re done! You now have access to the master app in each view.

Why a created method, instead of a ready method, you may ask? Because created is called before the DOM is evaluated, meaning Vue doesn’t even know the components have been instantiated yet. What this all amounts to is that the app variable is ready the moment the views are!

If you need access to the master app in a component, you need to pass it in as a prop. In the component’s module.exports, add ‘app’ to the props array. Then, just like before, whenever you instantiate the component using app=“{{@ app }}” . You can use this for any component.

Part 2: setting data for a particular view from other views or the main app

Since components are only loaded if you have component is set to them at one point in the app’s lifecycle, you can’t reliably access them directly from this.app. Instead, you can set data for them in this.app then pull it in the views. This is easier than it sounds! In app.js, add a new object to the data object, and call it viewData. In it add a name of each view you want to set data for and point it to an empty object. Example:

app.js

To set that data from a view, just go this.app.viewData[‘about-view’][‘foo’] = ‘bar’; . Then, to get it from the about-view, just go this.app.viewData[‘about-view’][‘foo’]. You could also shorten that by going into the ready method and adding: this.external = this.app.viewData[‘about-view’]. Be sure to add external as an empty object to the data function. Now you can just say this.external.foo to access it. Example:

views/analytics.js

This part really is just an example of taking advantage of the global access to the app’s data, nothing more. So take it with a grain of salt, because the real power is in Part 1 and 3.

Part 3: calling view functions

This is undoubtedly the most involved yet most useful part of this tutorial. For example, if I have a search bar in my navbar component, yet want to call a function in the search-results view, there is no good way to do it until now.

We need to be able to call functions if a view has been instantiated and if it hasn’t been. If it has been, we can just say viewModel[functionName](). If it hasn’t, doing so will result in an error. So we need to account for both situations…

Let’s add an setting to each view’s data in app.js called ready. Make it to default to false. Then, in the ready() function in each view, say this.external.ready = true; . Next, add an array called funcs_to_call . If the view has not been instantiated yet, we can add the name of the function to the array so the view can call it later in its ready() method.

To call the function on another view, we can say this.app.call(‘search-view’, ‘search’); This will call search-view.search() . If the view is instantiated, we can just call it directly. Otherwise, we have a little bit of a harder job.

Let’s deal with the first scenario first. Each view in viewData we need to have ready, funcs_to_call, and model. In each view’s ready() function, we need to set ready to true and model to this. Example:

views/analytics.js

This allows us to call the functions easily. In app.js, lets add a new method to the methods object called call(). Look at the gist below to see how it works:

app.js

Basically, it just tests if the view is ready. If it is, then it calls it using array syntax magic. Let’s do a test of our own: go to analytics-view.js and replace it to the following code:

views/analytics.js

When you load the page, it should instantly pop up with ‘i got called!’ as soon as the view is set in currentView!

Now we need to figure out what to do if ready is set to false. First, we just need to add the method name to the array, right? Add an else statement to the call method in app.js to push the function name onto an array:

app.js

Now we just need to check on ready() functions if there’s anything on the funcs_to_call array and call them if they exist. Add this to the end of the ready() function:

views/analytics.js

Now, when the view is loaded, it runs the method that was called!!!

Closing

To close it up, here’s the two files we’ve been working on, completed and done.

app.blade.php

app.js

views/analytics.js

Completed file structure. Here my view’s name is home instead of analytics, and I chose to use coffeescript instead of vanilla javascript, but the same principles apply.

You can use these two files as templates when using this design architecture in the future! I know I will.

To the left is the file structure I have for the Laravue repo. There are a couple of differences though: 1) I have a home view not an analytics view, and 2) I use coffeescript instead of vanilla JS. These aren’t biggies though!

What now?

In the future, I’ll no doubt be optimizing this and making it better. If you have any suggestions, just leave a response below! Your input is essential because the only way this can thrive is through community involvement.

The only way this can thrive is through community involvement.

In the future I may make a way to combine the ready and model variables in viewData functionally into one. But the additional code required may be more than I actually save, so I’m not sure on this one.

Something else I feel like this needs is a CLI. How about maybe php artisan make:view analytics and it will add all this boilerplate? Just an idea. Let me know if you’re interested in doing anything like this, because I think might be a bit beyond me.

For more info on how calling functions on objects using a string check out http://stackoverflow.com/questions/9854995/javascript-dynamically-invoke-object-method-from-string . It helped me out allot.

UPDATE: allow for arguments passed into view methods!

Now you can do this.app.call(‘foo’, ‘bar’, [‘arg1’, ‘arg2’, ‘arg3’]) and it will pass the array into the method. To retrieve them in a method just do args[0], args[1], args[2].

If you’re using coffeescript, it’s even easier! You can just use the … syntax for arguments. Yes, it’s technically possible without coffee, but it would look pretty ugly… Anyways here’s the coffeescript example:

Any input is highly welcome!