Architecting Forward ::>

Intro

In part 3 we built a complete working version of the Savings Goal Simulator where each view model and view mediator is nicely separated in its own module and corresponding source file. But what about the view? Well the goal of part 4 is to modularize and extract each view component.

View HTML Element

Now that we have a way to define how a view markup can be “included” in our page, we’ll just define our view “container” as a normal HTML element. We can use a “div” or any of the more semantically significant HTML5 elements like “section“:

<div id="my-view">
</div>

What we’re missing is a mechanism to:

associate the view element and the view markup together

render the actual view

optionally: data-bind any view model to the view

Renderable View

To render the markup for a given view we need a view rendering engine. As of this writing, the most prevalent option is jQuery Template.

Note: The jQuery Template implementation is rock solid and in use in many production web sites and internal applications. However the jQuery UI core team is in the process of deprecating its use and eventually replace it with another implementation. A serious contender for this is the jsRender + jsViews library. Other options also include Mustache and Handlebars. Although this tutorial is leveraging jQuery Template as its platform, I recommend you perform some additional due diligence once you’re ready to settle on library.

KnockoutJS goes one step further by integrating the view rendering engine with data-binding capabilities.

Note:KnockoutJS will allow pluggable view rendering engines over time, making it possible to use the one you choose (as long a as a plugin exists).

So KnockoutJS can take a view element with a corresponding “view template” and a view model, and render the resulting view.

Since we introduced the notion of “view template“, let’s spend some time getting familiar with the concept before proceeding with externalized views.

Introduction To Templating

Simple String-Based Templates

An string-based template is just a string containing text plus placeholders for variables. A placeholder is the name of the variable surrounded by curly braces and a prefixed by a dollar sign. So for example a template to display a time variable would look like:

You should be able to open the index.html web page in a browser and the div should now contain the rendered text with the first name and the current time.

Now we have an idea of how templating using jQuery Template is working. But in this example we are just using a string to hold our template. The library actually has a feature where you can store the template in a script tag on the same page.

Simple Inline Templates

The jQuery Template library also allow us to store the template in a script tag with a text/html type like so:

<script id='my-view-template' type='text/html'>
${firstName}, the time is now ${time}
</script>

The template can then be rendered with a slightly different form of the tmpl API:

var viewContent = $('#my-view-template').tmpl(viewData);

Having the template moved out of the code is a step in the right direction, but what we need is the ability to externalize the template to an external url. We’ll tackle that subject soon I promise, but in the meantime let’s look at what KnockoutJS brings us in terms of additional templating power.

KnockoutJS Databinding Support For Inline Templates

Up to now, we have had to write the actual code to: a) render the template, and then b) insert the resulting text in an HTML element. Well, let’s see what KnockoutJS can do to simplify our task. (This will be very handy especially when your main page has many views.)

As you know, KnockoutJS provides a “data-bind” attribute where you can specify the visual or behavioral aspects(e.g. value, text, visible, enable, …) to link to a value model or dependent observable function. When we want to data-bind an HTML element to both a template and a view model, we’ll use the following syntax:

It is even possible to specify a Javascript function in an afterRender template parameter. That function will the be invoked once the HTML element has been rendered. This can be useful if you need to dynamically attach new behaviors or event handlers to the produced content. For example, you could invoke jQuery UI component configuration APIs such as applying icons for a jQuery UI button.

If you try this out you will notice that the browser will indeed [wget] “fetch” the content of _my.view.html, and using the web developer tool of your browser you will actually see the template markup.

But if you try to access the content of the script element using jQuery using:

$("#my-view-template").text()

… you will notice that the content is empty! The browser engine has retrieved the content but did not store it in the actual DOM element for the script. This is due to the fact that a script of type text/html is not recognized as being a script to interpret.

So we need a mechanism to fetch and store the source. The approach consists of re-fetching the source using jQuery’s $.get API. Since it was fetched once, the content is stored in the browser’s cache so retrieval wil be very fast. We then store it in the DOM’s element.
Here is a simplistic example of what the code would look like:

The plugin will identify all scripts of type text/html and a source url ending in “.view.html“, including nested template scripts and request their parallel loading. Once all template scripts have been succesfully processed the success function is invoked.

Behind the scenes, jQuery ViewLoader relies on the jQuery concept of “Deferred“, an action which can be launched for “deferred” (i.e. later) execution, but can trigger a callback when completed. Actions can be grouped together so that a specific callback can be triggered once all of them have completed.

1. Create An External Template For The savings-goal-view

Our first step will be to create a new folder named view under scripts to organize our external views. Similarly to frameworks like Ruby On Rails, or ASP.NET MVC, we’ll create a subfolder named shared, for all views reusable across pages, then we’ll create a subfolder named index for views appearing on our index.html web page.

In the scripts/view/index folder, let’s create a file named _savings-goal.view.html. I used an underscore prefix to reflect the Rails convention of “partial views” being prefixed that way.

Then we can copy the savings-goal-view markup from index.html page and paste it in our new _savings-goal.view.html file.

Here is the visual depiction of how application.js coordinates with the ViewLoader:

Not so fast … if we run the app, we’ll get an error. This is due to the fact that the code from the createViewMediator function of our sgs.mediator.savingsgoal module is expecting the view to be available. But at this point in the execution the view as not yet been rendered. This leads us to one more refactoring.

4. Refactor The Savings Goal Mediator

The current implementation of the createViewMediator function of our sgs.mediator.savingsgoal module assumes that our view is in the DOM, but at the moment the ViewLoader plugin kicked off the InitializeViewMediators function, KnockoutJS has not been requested to apply bindings yet nor render any view templates.

So let’s refactor the code related to setting up the various data-bindings and invoking KnockoutJS into a new function named setupViewDataBindings:

Note: We had to add a new line to access the viewModel since it is now available in the DOM and accessible using the sgs.mediator.savingsgoal.getViewModel function. We also add to add a line to get the pageSettings using our new GetPageSettings function.

After having extracted some of the code the createViewMediator function consist of the following:

You probably noticed that there is no call yet to invoke our new setupViewDataBindings function! So where will it be invoked then? Well do you remember the comment about KnockoutJS exposing an afterRender callback function? (See back in KnockoutJS Databinding Support For Inline Templates.)

We’ll update the data-binding of savings-goal-view-container in our index.html, by assigning our new sgs.mediator.savingsgoal.setupViewDataBindings function to the afterRender attribute:

So What?

Externalizing your views makes your web pages more modular and easier to maintain, especially if you are working within a team as this allows you to assign different views (markup + code) to different individuals.

In the context of a “single page application” where you may have many views, these benefits are crucial to help you manage the complexity and allow you to grow the application organically / incrementally.

Another benefit of modularizing your views and view mediators will be that it will be easier to adapt and create different versions of the application for other channels, like a mobile web site or a mobile version of the application (using for example jQuery Mobile and PhoneGap).