javascript

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}} where detail 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 by v-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 DOM
  • data: The mutable state of the Virtual DOM
  • computed: The state of the DOM which is computed based on the mutable properties defined in data object. These properties are recomputed when ever the property they are referring to is updated
  • watch: 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!

Advertisements

Categories: javascript

Tagged as: , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s