RSS

Category Archives: underscore.js

Javascript templated MVC framework using dojo and underscore.js

Recently I’ve been working on a client-side MVC setup for web elements and I thought it may be helpful to others if I published it. I used the opportunity to have a look at Dojo as so far I’ve only used jquery for client-side code.

My first impressions of dojo as a noob are that it is better organised than JQuery in terms of loading js files etc, and has very powerful features for writing classes in javascript. The downside in my opinion is that the documentation is inferior to jquery, it is less intuitive and the declarative markup is intrusive.

For my MVC client app I therefore decided not to use dojo widgets, and instead use a templating solution to render the views.

For those not familiar with MVC, it is a design pattern used in Object Oriented applications. Basically it separates data from it’s presentation to the user by:

  • Storing the data in a ‘Model’, which updates all its associated ‘Views’ every time it is changed
  • Using a ‘Controller’ to interact with the UI to change values in the model

I’ve created a simple diagram of how our sample app will work, as you can see, the DOM (ie the UI) interacts with our Controller via event handlers. The Controller changes the appropriate values in its Model, which then updates all its associated views which are rendered back to the DOM:

The first task was therefore to find a suitable templating solution and wrap it in a class so I can potentially change the templating engine if the one I was using fell short in any way.

In jquery I use jqote for all my client-side templating. It’s simple, intuitive and very fast. However for this project I didn’t want to have to include jquery, therefore I opted for the engine in underscore.js, which supports logic within templates and is comparable in terms of performance to jqote.

Here’s the ‘wrapper’ class I wrote to encapsulate the templating functionality:

require(['dojo/_base/declare'],
    function (declare) {

        return declare('dojoSandbox.Templater', null, {

            template: null, // contains compiled template

            constructor: function(templateString) {
                this.template = _.template(templateString);
            },

            render: function(data) {

                return this.template(data);

            }
        })
    });

I now needed to write a base class for a templated view, with some common methods to render and a couple of handlers should I need them, we’ll call the base class _TemplatedView:

require(['dojo/_base/declare',
            'dojo/text',
            'dojo/dom-construct'
            ],
    function (declare, text, domConstruct) {

        return declare('dojoSandbox._TemplatedView', null, {

            // Populate this in your sub-class constructor
            controller: null,

            // Populate this in your sub-class constructor
            containerNode: null,

            // Populate this in your sub-class constructor
            template: null,

            constructor: function() {},

            // Called prior to rendering
            onRendering: function(model) { return true; },

            // Called after rendering is complete
            onRendered: function(containerNode) { return true; },

            // Implement this method in your sub-class
            _initialise: function () {

            },

            update: function (model) {

                if(this.onRendering(model)) {

                    domConstruct.empty(this.containerNode);
                    var html = this.render(model);
                    dojo.place(html, this.containerNode, 'last');
                }
                this.onRendered(this.containerNode);

            },

            // Override this method to return your view's html to the update() function
            render: function (model) {

                return this.template.render(model);

            }

        });
    });

I store an instance of my wrapped template in the template property, which is used by the render method whch injects the model into the template and spits out the resulting html. The update function uses dojo.place to insert this html into a specified containerNode.

Next we need to write a model, a view and a controller for our app. The app we’re writing doesn’t do anything useful, just takes input in a form and displays the inputed data. Once again though, the model will contain methods that are generally useful, therefore we create another base class _Model:

require(['dojo'], function (dojo) {
    return dojo.declare('dojoSandbox._Model', null, {

        views: [],

        constructor: function () {

        },

        addView: function (view) {

            this.views.push(view);

        },

        updateViews: function () {

            for (var i = 0; i < this.views.length; i++) {

                this.views[i].update(this);

            }

        }

    });
});

This class contains a property views which is an array of view instances. The updateViews() method iterates through this array, calling each views’ update() method and passing itself (the model) as the argument for the method.

Now this is done we can create a UserModel class that derives from it, and calls it’s base class’s updateViews() method if any set methods are called on its properties:

So the view is responsible for rendering the html for our app. We’ll write a UserModel class which contains all the properties we want to be able to change, and calls base.updateViews() when any of those properties are changed. this class extends our _Model class:

require(['dojo', 'dojoSandbox/_Model'], function (dojo, _Model) {
    return dojo.declare('dojoSandbox.UserModel', [dojoSandbox._Model], {

        firstName: null,
        surname: null,
        age: null,

        constructor: function () {

        },

        setFirstName: function (firstName) {

            this.firstName = firstName;
            this.updateViews();

        },

        setSurname: function (surname) {

            this.surname = surname;
            this.updateViews();

        },

        setAge: function (age) {

            this.age = age;
            this.updateViews();

        }

    });
});

Not really much to say about this, if any of the set methods are called, it calls its base class’s updateViews() method.

We now move onto the controller class. No need for any base classes here as everything in it is specific to our user application. The controller contains a reference to it’s model, which in theory could be shifted into a base class, but in this instance I didn’t think it worth doing due to the simplicity of the app:

require(['dojo'], function (dojo) {

    return dojo.declare('dojoSandbox.UserController', null, {

        model: null,

        constructor: function (model) {

            this.model = model;

        },

        setFirstName: function (firstName) {

            this.model.setFirstName(firstName);

        },

        setSurname: function (surname) {

            this.model.setSurname(surname);

        },

        setAge: function (age) {

            this.model.setAge(age);

        }

    });

});

For this app we are just calling methods in the model.

Now all this is done, we can finally move onto the view. We’ll use our _TemplatedView class as described above and extend it as a UserView class:

require(['dojo/_base/declare',
            'dojo/text',
            'dojo/on',
            'dojo/dom-construct',
            'dojoSandbox/_TemplatedView',
            'dojoSandbox/Templater'
            ],
    function (declare, text, on, domConstruct, _TemplatedView) {

        return declare('dojoSandbox.UserView', [dojoSandbox._TemplatedView], {

            constructor: function (controller, templateString) {

                this.controller = controller;

                if (templateString) {
                    this.template = new dojoSandbox.Templater(templateString);
                } else {
                    this.template = new dojoSandbox.Templater(dojo.cache('templates', 'UserTemplate.html')); // Use default template string
                }

                controller.model.addView(this);

            },

            _initialise: function () {

                var domNode = this.containerNode;
                var controller = this.controller;

                on(domNode, '#firstName:change', function () { controller.setFirstName(this.value); });
                on(domNode, '#surname:change', function () { controller.setSurname(this.value); });
                on(domNode, '#age:change', function () { controller.setAge(this.value); });

            },

            update: function (model) {

                this.inherited(arguments);
                this._initialise();

            },

        });
    });

The constructor takes a controller as a param, and an optional template string. If no template string is provided, we’ll use a default one stored in an html file (I’ll go through this later).

This means that we can instantiate multiple instances of this View class using different templates which therefore render differently. It gives us a bit of added flexibility.

As you can see, we are caching the default template string using dojo.cache. Once the new Templater instance is created, the template is compiled ready for rendering using the injected object (more on this later).

The update() method overrides the same method in our _TemplatedView class, the only thing is adds to this however is a call to our _initialise() method which adds any handlers to the newly rendered template. In the handler code you’ll see that we are just caling the set methods in the controller instance.

Our default template looks like this:


    <label for="firstName">First Name</label>
    <input id="firstName" type="text" value="<%= firstName %>" />

    <label for="surname">Surname</label>
    <input id="surname" type="text" value="<%= surname %>" />

    <label for="age">Age</label>
    <input id="age" type="text" value="<%= age %>" />

By default, our templating engine uses the blocks to output values. If you look at the render() method in our _TemplatedView class, you’ll see that we are passing the model (which in this case will be an instance of UserModel) into the template. So the properties inside the blocks correspond to properties in our UserModel class. Bare in mind that if any of these properties have null values, ‘null’ will be inserted into the template which is not ideal.

Now thats all done, we can wire it all up in a page:

@{
    ViewBag.Title = "Home Page";
}

@section Javascript
{
<script type="text/javascript">// <![CDATA[
require([
            "dojo/parser",
            "dojo/Stateful",
            "dojo/domReady!",
            "dijit/Tooltip",
            "dijit/_TemplatedMixin",
            "dijit/form/Button",
            "dojoSandbox/_TemplatedView",
            "dojoSandbox/UserView",
            "dojoSandbox/UserController",
            "dojoSandbox/UserModel"
        ], function () {

            // Create the model
            var model = new dojoSandbox.UserModel();
            model.firstName = '';
            model.surname = '';
            model.age = '';

            // Create the controller, attaching the model to it
            var controller = new dojoSandbox.UserController(model);

            // Create 3 different templated views, specifying the controller. The controllers model will have the view assigned to it
            var view1 = new dojoSandbox.UserView(controller);
            var view2 = new dojoSandbox.UserView(controller, '



<h1><%= firstName %>&nbsp;<%= surname %>, aged <%= age %></h1>

');
            var view3 = new dojoSandbox.UserView(controller, '<textarea><%= firstName %>\n<%= surname %>\n<%= age %></textarea>');

            // Specify container nodes for each view
            view1.containerNode = dojo.byId("view1Container");
            view2.containerNode = dojo.byId("view2Container");
            view3.containerNode = dojo.byId("view3Container");

            // Refresh/render the views
            model.updateViews();

        });
// ]]></script>
}</pre>
<h2>View 1</h2>
<div id="view1Container"></div>
<h2>View 2</h2>
<div id="view2Container"></div>
<h2>View 3</h2>
<pre>

You’ll then need to add script tags to the head in your master page to load and configure dojo and our other classes:


<script type="text/javascript" src="@Url.Content(&quot;~/Scripts/underscore.js&quot;)"></script><script type="text/javascript">// <![CDATA[
        dojoConfig = {
            baseUrl: '@Url.Content("~/Scripts")',
            isDebug: true,
            tlmSiblingOfDojo: false,
            packages: [
                { name: 'dojo', location: 'dojo' },
                { name: 'dijit', location: 'dijit' },
                { name: 'dojox', location: 'dojox' },
                { name: 'dojoSandbox', location: 'classes' },
            ]
        };

// ]]></script>

<script type="text/javascript" src="@Url.Content(&quot;~/Scripts/dojo/dojo.js&quot;)" data-dojo-config="parseOnLoad: false, async: true, mvc: {debugBindings: true}"></script>

So the project structure looks like this:

Although I use asp.net MVC for this example, obviously it applies to any type of project and is easily adaptable. As you can see, first we create the model and set its properties, then we create the controller, inserting our model in the constructor. Then we instantiate the views which all use the same controller, but different templates.

All that’s left to do is specify a containerNode for each view. These are elements within our page.

For more complex apps this ‘wiring up’ could be encapsulted in another class with a root container.

I hope this helps anyone who wishes to implement a client-side MVC framework using dojo and templates. The full project can be downloaded here

 
2 Comments

Posted by on August 24, 2012 in dojo, javascript, jquery, underscore.js