Moving From Mustache.js and jQuery to Vuejs for Client Side View Management Reactively
Introduction
A month ago I wrote a post on using Mustache.js for client-side templating. I am pretty excited about the way we use Mustache.js, jQuery Ajax Client to build views. Its simple and does the work for us.
But? it’s not reactive!, we manage a lot of DOM manipulation which can often lead to a mess. And for someone reading the code for the first time, it would take a while to understand the flow.
What if we could use a reactive javascript framework? Or How about AngularJS?
Javascript Framework Choice
There are lots of Mustache templates lying around, we want to be able to reuse them and not just rework! Our first thoughts were to adopt Angular JS 1 or 2 but the way it changed drastically from 1 -> 2 -> 4 and the new TypeScript in ng4 just made us drop the idea.
I then played around with ReactJS in a workshop, it was amazing! But the workshop was Node.js based! So I didn’t exactly know how it would all fit in our maven-based Java applications. Then there was JSX which means our Mustache templates cannot be reused!
At a training on Elasticsearch, a fellow developer from Shanghai, China (of course he would know a hot framework in their region) introduced me to Vuejs, it was during the time Vuejs 2.0 was around the corner. And then I tried it out only recently and I was impressed, especially by the standalone version which makes it extremely easy to adopt in our maven-based setup while reusing our Mustache.js templates.
Enough of talk and let me show you how I easily moved my Mustache sample demonstrated here to Vuejs based.
Vuejs Templates
Below is the part of the code from the HTML which has Vuejs related directives and the Mustache like template markup:
<div class="container"> <div class="page-header"><h1>Vuejs Demo</h1></div> <div class="row"> <div class="col-md-offset-3 col-md-6"> <h3>Select user</h3> <select id="git-users" class="form-control" v-model="gituser"> <option value=""></option> <option value="facebook">Facebook</option> <option value="google">Google</option> <option value="netflix">Netflix</option> <option value="flipkart">Flipkart</option> <option value="amzn">Amazon</option> <option value="microsoft">Microsoft</option> </select> </div> </div> <div class="space"></div> <div class="row" v-cloak> <div class="col-md-offset-3 col-md-6"> <div class="alert alert-info center" v-if="!userSelected"> <i class="fa fa-exclamation-triangle"></i> Select a GitHub User </div> <div v-if="userSelected"> <div class="center" > <div class="alert alert-info" v-if="loadingDetail"> <i class="fa fa-spinner fa-spin"></i> Loading Detail... </div> <div v-if="!loadingDetail"> <img :src="detail.avatar_url" class="git-user-avatar" /> <div class="space"></div> Github User <a :href="detail.html_url">{{detail.login}}</a> <div class="space"></div> Name <a :href="detail.blog">{{detail.name}}</a> <div v-if="isBio" class="space"> {{detail.bio}} </div> </div> </div> <div class="space"></div> <div> <div class="alert alert-info" v-if="loadingRepos"> <i class="fa fa-spinner fa-spin"></i> Loading Repos... </div> <ul class="list-group" v-if="!loadingRepos"> <li class="list-group-item" v-for="item in repos.items"> <span class="badge"> <i class="fa fa-star git-stars"></i> {{item.stargazers_count}} </span> <a :href="item.html_url">{{item.full_name}}</a> </li> </ul> </div> </div> </div> </div> </div>
Following are the transformations I applied to my Mustache templates:
- Property references to be accessed via their parent property so
{{name}}
became{{detail.name}}
wheredetail
is a property in my Vuejs Data object which contains the GitHub user detail - Mustache braces in attributes were replaced by Vuejs directives and property reference without the braces so
src="{{avatar_url}}"
became:src="detail.avatar_url"
- Looping uses
v-for
- Conditional rendering using
{{#prop}}{{/prop}}
replaced byv-if
directive - Added a empty directive
v-cloak
to not show the template until Vuejs has been initialized. This prevents{{property}}
appearing in the HTML. - Added few properties in my data object to control the loading and user not selected state
Vuejs State
I carefully thought about the states the UI can go through which is:
- User not selected
- User selected and detail loading
- User selected and repos loading (both happen async)
- Detail loaded
- Repos loaded
So I declared properties for each of the above scenarios into my data
object to initialize my Vue object as shown below:
vm = new Vue({ el: ".container", data: { gituser: "", loadingDetail: false, loadingRepos: false, detail: {}, repos: {} }, computed: { userSelected: function(){ return this.gituser != ''; }, isBio: function(){ return this.detail.bio != '' && this.detail.bio != null; } }, watch:{ gituser: function(){ if ( this.gituser){ getUserDetail(this.gituser); getMostStarredRepos(this.gituser); } } } });
In the above code:
el
: The dom element used to build the Virtual DOMdata
: The mutable state of the Virtual DOMcomputed
: The state of the DOM which is computed based on the mutable properties defined indata
object. These properties are recomputed when ever the property they are referring to is updatedwatch
: Reacting to change in the state of observed properties
Vuejs State Management
I have bound the value of the selection to the gituser
property of the data
object using v-model
. This is a two way binding which means when the value in the selection is changed, it automatically updates the gituser
property and which in turn triggers the watcher method attached to the property.
In the watcher method, we invoke the RESTful APIs using jQuery’s Ajax API. There is no official recommendation for accessing remote services in Vuejs, so I thought of sticking with what I already have. But the methods which invoke the APIs are very simple and just manipulate the Vuejs state and not the DOM as shown below:
function getUserDetail(username){ vm.loadingDetail = true; $.getJSON('https://api.github.com/users/'+username, function(data){ vm.detail = data; vm.loadingDetail = false; }); } function getMostStarredRepos(username){ vm.loadingRepos = true; $.getJSON('https://api.github.com/search/repositories?q=user:' +username+'&sort=stars&per_page=10', function(data){ vm.repos = data; vm.loadingRepos = false; }); }
So whenever the state i.e properties defined in the data
object changes, Vue updates its Virtual DOM and thus updates the view.
Once you have the code running in the browser, you can open the console and try these:
//this will load the Vuejs detail vm.gituser = "vuejs" //will load a message vm.gituser = ""
Conclusion
You can find the complete Vuejs sample here and can also compare with the Mustache.js version available here.
I have tried to use a standalone version of Vuejs (i.e by including Vue.js source via the script tags) to create a reactive application reusing much of the infrastructure I had for Mustache.js.
There are few places where I can improve like:
- Create a loading component which can be reused,
- Create user detail component,
- Create repos item component and so on.
I will cover these in subsequent posts. For now, this is an amazing improvement from Mustache.js. I also have planned few posts around using standalone Vuejs in a Spring Boot based application which is much more interesting, but that’s for sometime in future.
PS: I am not comparing Vuejs with Mustache.js instead it’s about moving from Mustache.js based client templates to a reactive UI leveraging most of the Mustache work!
Published on Web Code Geeks with permission by Mohamed Sanaulla, partner at our WCG program. See the original article here: Moving From Mustache.js and jQuery to Vuejs for Client Side View Management Reactively Opinions expressed by Web Code Geeks contributors are their own. |