Introduction
In one of my projects, I am working on a single page application (SPA), using vuejs to load the content into the user interface. To route to different pages, the application will make use of the hash location to identify and load the respective content and template.
The vuejs documentation features a rudimentary way of generating the content based on the path, the solution looks very promising, we will be using it as a starting point in this article.
Trying out the example
The routing example only included static templates. But that is not very useful in itself. I want to be able to route with a normal template where the interface can be data bound to update dynamically with changes in the application state.
const NotFound = { template: '<p>Page not found</p>' }
const Home = { template: '<p>home page</p>' }
const About = { template: '<p></p>' }const routes = {
'#/': Home,
'#/about': About
}new Vue({
el: '#app',
data: {
currentRoute: window.location.hash,
title: 'asdf'
},
computed: {
ViewComponent: function() {
return routes[this.currentRoute] || NotFound;
}
},
render: function(h){
return h(this.ViewComponent)
}
})
So I replaced the template to include some variables from the data
object. Error: the template doesn’t recognize the variable. Now we have to figure out how to make vue recognize and register the variables.
Render the template
We will start off by exploring how the interface is rendered. Whenever the window.location.hash
changes, the ViewComponent()
changes. The function returns an object representation of a Vue Component. So first, we will have to understand how components work.
The documentation, introduced components with the prop
attribute. prop
is used to pass information from parent to child components. Maybe we can use it to insert information into the user interface?
nope… didn’t work
new Vue({
el: '#app',
data: {
currentRoute: window.location.hash
},
computed: {
ViewComponent: function() {
var template = routes[this.currentRoute] || NotFound;
template.prop = {
title: 'test'
}; return template;
}
},
render: function(h){
return h(this.ViewComponent)
}
});
Information not recognized???
There is a gap in my understanding of how information is being passed into vue components, how the object makes use of the render
function. To fill this gap, we have to go back to the documentation.
In the render
function, the component is created with the createElement
function. By inspecting the result of h(this.ViewComponent)
, we can have a better understanding of what the vue instance contains.
The documentation included this section which shows how the render
function is being used. The render function takes in either the template
attribute or the element’s outerHTML
as input and compiles it into a chain of functions. (details here)
The documentation page on the render function includes some pretty interesting details about using prop
too.
Rendering components using `prop`
This part of the documentation covers the use of prop
to create customized components. The prop
attribute provides information to describe the component. In the example, the prop
attribute is used to alter the render function to generate different types of components.
But prop
doesn’t look like our solution. What we are trying to do is to allow the render function to bind data to the DOM elements in the interface, not making changes the template of the components. prop
doesn’t look like the thing we need.
I need to look further
I delved deeper into the results of the render function to search for clues on binding data to the component’s template.
As I explored through the different attributes, the data
attribute caught my attention. I did a search on vue component’s data
attribute. Turns out, this data attribute (or method, if you will) is pretty special.
Unlike a vue instance where the data
attribute can be an object, for a component it must be a function.
This is to allow support for multiple components. When data
is a function, it allows your component to return a different object for different instances of the component. This way, you can segregate your component’s data and different components will be bound to different data objects. A rather elegant solution.
new Vue({
el: '#app',
data: {
currentRoute: window.location.hash
},
computed: {
ViewComponent: function() {
var template = routes[this.currentRoute] || NotFound;
template.data = function(){
return {
title: 'asdf'
};
}; return template;
}
},
render: function(h){
return h(this.ViewComponent)
}
});
So what I did is to include the data
function inside the object which is passed into the render
function of the vue instance. And it worked great, now the interface recognizes the data and displays the right value.
The whole VueJS model
Now that we got the code working, let’s figured out how the vue object is structured and rendered.
Each vue instance is something like a component, the whole object can be imagined to be a giant component. How a vuejs instance is rendered depends on its render
function. In the default render
function, vuejs checks if the component had a template
attribute. If it finds one, vue will compile the template
string into a string of functions (see documentation for more details).
If the template
attribute doesn’t exist, vue will use $el.outerHTML
instead, where $el
is the DOM element your vue object is attached to. This gives developers the convenient option of including the template in the HTML body directly, so they wouldn’t need to mess with Javascript for simple data binding.
In the router example, you will be creating different vue components for each different path. What determines which interface to load is the template
attribute. It is a template string that determines what will be loaded in the DOM element. It is not any different from the template string that you use normally, you can create HTML elements that are bound to different data as well.
As for the structure of the design, there is a vue instance attached to the main HTML wrapper div#app
. The object is then set up such that any change in the hashpath will trigger the vue object to re-render. This is done by adding window.location.hash
to the object’s data
as one of the watched variables.
When the main vue object is triggered, the computed
variables are reloaded, and ViewComponent
be will recalculated. This will return an object that describes the component to be rendered. It will include main attributes required, such as template
, and data
.
Of course, the key feature enabling all of this is the render
function of the vue instance. As seen in the flowchart, the render
function is triggered when there is a change in any of the watched values. Our render
function takes the ViewComponent
object and pass it into the createElement
function. This will load the corresponding interface, depending on the template
that you have defined for each different route.
Of course, that is just the basic version of a vuejs router. Some additional features you might consider adding will be the support of route specific functions. This will allow you to do things such as user checks, redirecting, and in my case: load data using ajax calls.
The end
That’s all to the design of a vuejs router. The rough idea is to redraw the app view according to the current route, as determined by the hashpath. Knowing this, you will be able to extend the idea further to add support to more features. If you have any questions or know of even better routing design, do leave it in the comments below.