MVVM and jQuery: Designing Maintainable, Fast and Versatile Sites
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="http://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="http://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.
Data binding using the DataLink plugin
- 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.
Check out the fully functioning demo here: RSS
Reader
You can also download the full source at Gitub.