MVVM and jQuery: Designing Maintainable, Fast and Versatile Sites

Saar Yahalom | June 15, 2011

 

Developing client-side web applications is becoming standard practice for building cross-platform solutions. It helps bridge the gap between different mobile devices and different operating systems.

In a world where more and more services are moving to the cloud, where websites are charged by the number of bits they generate and the number of CPU cycles they consume. It makes a lot of sense and actually saves money to design for nearly pure client-side web applications.

Choosing a right design pattern to follow when building such web applications is key for designing maintainable, fast and versatile sites. In this post, I would like to suggest an MVVM design approach for building pure client-side web applications with the help of jQuery, templating and data-binding.

The Model-View-ViewModel (MVVM) Design pattern

MVVM is a close relative of the classic MVC pattern. The main difference is the addition of commands and data-binding. The addition of these two concepts allows us to further disconnect the view from the controller (or viewmodel in our case). Using commands we can tie a behavior to a specific view action that is defined in our viewmodel, where data-binding allows us to link specific view attributes to viewmodel properties. Changing the property in the viewmodel will be reflected in the view and vice versa.

Pure client-side web application design

Before diving into an example here is a diagram representing the web application design.


Figure 1, Web application MVVM design

The view is built using pure HTML+CSS, where custom html attributes in the view controls the behavior. The viewmodel is written in pure JavaScript with the help of jQuery. Last, the model is based on JavaScript objects that come from JSON web services.

Using the suggested design, applications can be cached on the client after being retrieved from the server. Besides the communication with the web services, the web application can run without a dedicated webserver, allowing fully offline scenarios.

Building an RSS Reader Web Application

We are going to be building a simple RSS reader application using the MVVM design pattern. The RSS reader will be written in pure JavaScript with the help of jQuery and two important jQuery plugins, the data templates plugin and the datalink plugin.

The RSS reader will allow us to enter an RSS feed URL, load the feed items and present them to the user. We are going to be relying on Google Feed API to do the heavy lifting of fetching the actual feed data and converting it to JSON, effectively this is our JSON web service.

The View (Presentation Layer)

First, we will set the web application view using HTML+CSS.

The HTML markup determines the structure of the view while the html custom attributes provides hints of which commands to attach to which actions.

For example, consider the following markup:

<button class="flat" command="searchCommand">Search<button>

The custom attribute command is used to hint that the clicking the button should trigger the searchCommand behavior.

Here is how our RSS reader will look.

Let’s take a look at the actual markup.

<!DOCTYPE html>
<html xmlns="https://www.w3.org/1999/xhtml">
<head>
    <title>MVVM RSS Reader</title>
    <link rel="Stylesheet" type="text/css" href="rssreader.css" />
    <script src="https://www.google.com/jsapi?key=INSERT_YOUR_KEY" type="text/javascript"></script>
    <script src="https://ajax.microsoft.com/ajax/jquery/jquery-1.6.min.js" type="text/javascript"></script>  
    <script src="https://www.google.com/uds/?file=feeds&v=1" type="text/javascript"></script>
</head>
<body>
    <div class="mainDiv">
        <div class="inputDiv">
            <label>RSS Feed Url</label>
            <div>
                <input id="feedUrlInput" class="feedUrlInput" type="text" datalink="rssUrl" />
                <a class="link" command="loadFeedCommand" cmdArg="feedList">load feed</a>
            </div>
        </div>
        <script id="feedItemTemplate" type="text/x-jquery-tmpl">
        <li class="feedListItem">
            <a class="link" href="${link}" target="_blank">${title}</a>
            <label class="itemDate">${publishedDate}</label>
            <p class="itemcontent">${contentSnippet}</p>
            <a class="link morelink" command="moreCommand" cmdArg=".itemcontent">more</a>
        </li>
        </script>
        <ul id="feedList" template="feedItemTemplate" class="feedList">
        </ul>
    </div>
    <script src="jquery.tmpl.js" type="text/javascript"></script>
    <script src="jquery.datalink.js" type="text/javascript"></script>
    <script src="MVVMUtils.js" type="text/javascript"></script>
    <script src="ReaderViewModel.js" type="text/javascript"></script>
</body>
</html>

As you can see, with relatively light markup we have determined the look of our view along with our desired behavior. Let’s take a closer look on the MVVM concepts introduced here.

Attaching Commands to View Actions

Pressing the ‘load feed’ link should cause the reader to load the feed and present it.

<a class="link" command="loadFeedCommand" cmdArg="feedList">load feed</a>

The ‘load feed’ link is decorated with a command="loadFeedCommand" attribute hinting its desired behavior, and the custom attribute cmdArg="feedList" hints that an html list element called feedList should present the loaded feed items.

Deciding on the degree of separation between the view and the viewmodel is up to the developer. There are times when passing element ids as command arguments is not very convenient and accessing a predefined DOM element is an easier solution. The possibility to separate your viewmodel from the view without it intimately knowing each element by name is powerful but not sacred.

Data Templates

After loading the feed items we want to format and style them nicely. This clearly should be a part of the view the problem is that when we load the view we don’t have the actual elements to style. Data templates help us solve this problem in a nice manner. The styling is done in advanced while the loading and formatting is done after the view is already presented.

This is our feed item data template.

<script id="feedItemTemplate" type="text/x-jquery-tmpl">
    <li class="feedListItem">
        <a class="link" href="${link}" target="_blank">${title}</a>
        <label class="itemDate">${publishedDate}</label>
        <p class="itemcontent">${contentSnippet}</p>
        <a class="link morelink" command="moreCommand" cmdArg=".itemcontent">more</a>
    </li>
</script>

The marking ${propertyName} is used to define which property of the model should be placed in instead of the marking.

Data Binding

The feed URL should be synchronized with the viewmodel as it will be connecting that URL.

<input id="feedUrlInput" class="feedUrlInput" type="text" datalink="rssUrl" />

Data binding the value of the input element feedUrlInput to the viewmodel rssUrl property allows us to keep it in sync. Please notice that the input field must have a unique id in order for the datalink mapping to work properly. Later on we will see when this binding is actually registered.

ViewModel (Behavioral Layer)

The viewmodel is responsible for the actual logic and data flow of the view. Having the viewmodel separated from the HTML markup allows us to lazy-load the JavaScript file only when it is needed, thus allowing faster loading of the view itself. In an application with multiple views where each view has a dedicated viewmodel, we can load the necessary code only if the view is actually being viewed.

Let's take a look at the viewmodel of our sample web application.

(function (window, $, MVVMUtils, google) {
    "use strict";
 
    window.ReaderViewModel = {
        rssUrl: "",
        loadFeedCommand: function (arg) {
            var self = arg.data,
                feed = new google.feeds.Feed(self.rssUrl),
                containerId = $(this).attr("cmdArg");
 
            // Calling load sends the request off.  It requires a callback function.
            feed.load(function (result) {
                var feedList = $('#' + containerId),
                    templateId = feedList.attr("template"),
                    template = $('#' + templateId);
 
                if (!result.error) {
                    feedList.empty();
                    template.tmpl(result.feed.entries).appendTo(feedList);
                }
            });
        },
        moreCommand: function () {
            var commandingElement = $(this),
                // an assumption that our parent is the templated item
                templatedElement = commandingElement.parent(), 
                feedEntry = templatedElement.tmplItem().data,
                contentSelector = commandingElement.attr("cmdArg"),
                contentElement = templatedElement.children(contentSelector).first();
 
            contentElement.html(feedEntry.content);
            commandingElement.hide();
        },
        bind: function () {
            MVVMUtils.bindCommands(this);
            MVVMUtils.bindDataLinks($(document), this);
        }
    };
 
    $(document).ready(function () {
        window.ReaderViewModel.bind();
    });
} (window, jQuery, MVVMUtils, google));

The viewmodel contains properties, commands and a single bind function. The bind function is responsible for applying the glue logic with the help of the MVVMUtils class. This is where most of the magic happens, the commands are bound to their actual implementation and the data-binding between the view and the viewmodel is initiated.

When the document’s loading is complete we call the bind function of our RSS reader viewmodel.

The Model – Retrieving JSON data from web services

Complex applications often need to pull data from external data sources such as databases. The mediator between the data source and the web application should use a data format that can be easily read by the web application.

The JSON format is a very popular serialization format for data in web applications. Most web services platforms such as WCF support this format. In our application design they are the only pieces of code, which run on the server side.

MVVM with jQuery - Behind the scenes

This section is devoted to show how the actual glue logic works with the help of jQuery. I will go over briefly on the implementation details of the MVVMUtils class. This section is more technical and can be skipped.

Binding the commands using the ‘live’ jQuery functionality

bindCommands: function (viewModel) {
    /// <summary>
    /// Binds a view model’s commands to the view
    /// </summary>
    /// <param name="viewModel" type="Object">A view model to bind</param>
    var key,
        jqElem = $(document);

    for (key in viewModel) {
       if (endsWith(key, "Command")) {
           jqElem.selector = "[command='" + key + "']";
           jqElem.live('click', viewModel, viewModel[key]);
       }
    }
}

In order to allow the commands to trigger for dynamically loaded content we use jQuery’s live() function. In our example, the viewmodel’s moreCommand is still triggered correctly even though we load the feed items after the call for bindCommands has been made. The way it works is that jQuery registers a single event handler for all elements corresponding to a given selector. The selector is evaluated when the event reaches the document element. I chose to bind to the 'click' event; you can easily add a custom attribute for selecting the event type to bind to.

bindDataLinks: function (view, viewModel) {
    /// <summary>
    /// Initiates the data links for whole view with the given view model
    /// </summary>
    /// <param name="view" type="jQueryObject">A view to data link</param>
    /// <param name="viewModel" type="Object">A view model to data link</param>
    this.unlink(view);
    this.link(view, viewModel);
},
unlink: function (jqElement, obj) {
    jqElement.unlink(obj);
},
link: function (view, viewModel) {
    var mapping = this.getDataLinkMapping(view),

    view.link(viewModel, mapping);
},
getDataLinkMapping: function (jqElement) {
    var mapping = {};
    // iterate over all elements with data link attributes and generate a mapping
    // between the element 'value' attribute to a specific JavaScript context property
    // usually the context will be view model object
    jqElement.find('[datalink]').each(function () {
        var memberName = $(this).attr('datalink'),
            target = this.id,
            setter = $(this).attr('dataLinkSetter') || "val";
 
        if (memberName && target) {
            mapping[memberName] = {
                name: target,
                convertBack: function (value, source, target) {
                    if (setter === "text" || setter === "val" || setter === "html") {
                        $(target)[setter](value);
                    } else {
                        $(target).attr(setter, value);
                    }
                }
            };
        }
    });
 
    return mapping;
}

For each element with a datalink attribute we add a mapping between the element’s value attribute and the viewmodel desired property. A back conversion function is defined for the mapping to allow the user to choose a different attribute other than the value attribute.

After a mapping is created for the entire view we use the datalink plugin along with the mapping to link the view with the viewmodel. Here is the code for activating the datalink.

var mapping = this.getDataLinkMapping(view),
view.link(viewModel, mapping);

Final Thoughts

The design pattern suggested here provides a good separation of concerns. The view can be built by a designer and later decorated with behavioral custom attributes (commands hints). The viewmodel can be built and tested separately without relying on the view being completed.

The whole web application is basically a static set of HTML, CSS and JavaScript files. There are no moving parts on the server aside from the web services. This follows closely with how rich internet applications are built today in Silverlight and Flash.

Hopefully, we will soon start to see dedicated light MVVM frameworks for JavaScript which will fill the missing gaps.

Download the full source at Github.

 

About the Author

Saar is a front end developer at Microsoft. He enjoys the fast pace of web technologies and regularly complains about web applications architecture.

This is his first attempt to do something about it.