Mama said knock you out!

by dotnetnerd 20. May 2011 13:35

In my last post I looked at how WCF Web API plays well with JQuery Templates. A former colleague of mine questioned if it could be used in all scenarios. This is a rather broad question, but I do think it can be used for most of your templating needs.  So in this post I will give an example of what can be done just with templates – and then I will take a look at KnockoutJS to make my templating scenario even sweeter.

JQuery Templates – beyond the basics

In my last post I only showed a simple scenario with straight forward bindings where properties were just printed out. So to show off some of the capabilities I will need a model with a few levels, and some data to work with. Normally this would come from some webservice, but for the sake of the demo it could be e.g. a list of products like this.

var products = [
    {
        Name: "Lamp", Price: 100, Sale: false, Description: "Lavalamp, a blast from the past",
            Countries: ["Denmark", "Norway"]
    },
    {
        Name: "Watch", Price: 900, Sale: false, Description: "This watch is just <strong>awesome</strong>",
            Countries: ["Norway"]
    },
    {
        Name: "Jeans", Price: 500, Sale: true, Description: "Classic bluejeans",
            Countries: ["Denmark"]
    }
    ,
    {
        Name: "Pen", Price: 0, Sale: true, Description: "Regular blue pen",
            Countries: ["Finland", "Norway"]
    }
];

Now we will need to define a template, can can show off some of what we can do. I have picked out some of the features that when they are combined will demonstrate that that we can do pretty complex templating. First off the price is formated and marked in yellow if it is on sale. Then the description containing markup is written out. Finally the list the countries is enumerated and printed. To do this we will need the following markup.

<style>
    ul {width:350px;}
    .onSale {background:yellow;}
</style>
<script id="productTemplate" type="text/html">
<li>
    <h3>${ Name }</h3>
    <p {{if Sale}} class="onSale" {{/if}}>
    Price: ${ formatPrice(Price) }</p>
    <p>{{html Description }}</p>
   
    {{each Countries}}
        <p>${$index +1}: ${$value}</p>
    {{/each}}
</li>
</script>

<ul id="products"></ul>

As the example shows I first define a css class to handle marking prices as on sale by making the background yellow. Then it is used in the template if the product is on sale – for simplicity I have a bool property, but it could just as well have been an expression or call to a function that returnes a boolean. To show the price I call a function that handles formatting. The use of conditionals, loops and functions should be enough to do almost anything.  To run this you bind the data to the template as we did in the previous post like so

$("#productTemplate").template("productList"); 
$.tmpl("productList", products).appendTo("#products");

KnockoutJS – MVVM for JavaScript

If you haven’t heard about it before KnockoutJS is a Javascript MVVM framework built on top of JQuery Templates. Basically it provides you with the power to bind a model to UI elements, and have them syncronize when either is changed. For you this means less boilerplate code, more power, going home early, and most likely fewer bugs.

Like other MVVM frameworks it is built around the concept of observables – which allow you to get notified when an object changes of items are added/removed from an array. The knockoutjs.com site has good documentation but, just to demonstrate lets look at doing a simple viewmodel. To set this up we could do something like this.

var viewModel = { FirstName: ko.observable("Jens"), LastName: ko.observable("Hansen")  };   
viewModel.FullName = ko.dependentObservable(function()
    { return this.FirstName() + " " + this.LastName(); }, viewModel);

The first line of code shows how to create properties, and the second line adds a property that depend on values from the other properties – which is something we run into all the time. With this in place we can bind to dom elements by adding some attributes, and calling ko.applyBindings(viewModel). The markup could for instance be like the following.

FirstName: <input type="text" data-bind="value: FirstName"></input>
LastName: <input type="text" data-bind="value: LastName"></input>
FullName: <span data-bind="text: FullName"></span>

Now any changes to the text boxes will automagically be reflected in the model. The important point here is that the value of databind consists of the name of the property on the dom-element and the name of the property on the viewmodel.

Connecting the dots

As I first mentioned the model will most likely start out as JSon comming from a webservice of some kind. After parsing the JSon this leaves us with the job of mapping each property, so it becomes observable, before the model can be used in bindings. This would become tedious very quickly, but thankfully there is a mapping plugin for KnockoutJS.

To illustrate how this can be used we could work with our products array, and bind them to a template. First we will then make a viewmodel that has a property with the list of products that we map as observable.

var viewModel = { items: ko.mapping.fromJS(products)};
ko.applyBindings(viewModel);

Then we change the template, so it uses KockoutJS bindings. As you see calling functions and applying css is done a little differently, but it is still pretty simple to do.

<script id="productTemplate" type="text/html">
<ul>
    {{each items}}
    <li>
        <h3 data-bind="text: Name"></h3>
        <p data-bind="text: function() { return formatPrice(Price())}(), css: {onSale: Price() === 0}"></p>
        <p data-bind="html: Description"></p>
        <p data-bind='template: { name: "countryTemplate", data: Countries}'></p>
    </li>
    {{/each}}
</ul>
</script>
<script type="text/html" id="countryTemplate">
    {{each $data}}
        <p>${$index +1}: ${$value}</p>
    {{/each}}
</script>

Now we should be up and running, so any changes to the model are reflected in the dom. To try this out you could add an element or change an existing element.

viewModel.items.push(
        ko.mapping.fromJS(
            {
                Name: "Car", Price: 100000,
                Description: "Lightning <b>fast</b>", Countries: ["U.S.A"]
            }
        )
    );
    viewModel.items()[0].Price(125);

Concluding thoughts

To me this is just what I need in applications that are heavy on users creating and updating content. Having to keep the dom and a model in sync in javascript is very error prone, and not one of the most fun parts of building a nice UI. So working with bindings is something I have missed since I played around with Silverlight. From what I have seen it looks pretty promising on the performance front as well – however it is the most prominent question I have left to be answered when I get to use it in a real bigscale project.

Comments (2) -

Niels Brinch
Niels Brinch Denmark
6/2/2011 6:49:23 AM #

Thanks, that is beautiful. It seems there will be very few scenarios where this could not be used directly - and those few scenarios could probably be handled by writing a little wee of html from codebehind and inserting it in the template. I mean, the real world is full of exceptions, so even now, I'm not completely convinced that it can be used 'clean' in all scenarios.

DotNetNerd
DotNetNerd Denmark
6/2/2011 12:02:18 PM #

Let me know if you run into anything Smile I will be doing a little home project myself, where I will be taking it to the limit, so I am excited to see how clean I can keep it...

Pingbacks and trackbacks (1)+

Who am I?

My name is Christian Holm Diget, and I work as an independent consultant, in Denmark, where I write code, give advice on architecture and help with training. On the side I get to do a bit of speaking and help with miscellaneous community events.

Some of my primary focus areas are code quality, programming languages and using new technologies to provide value.

Microsoft Certified Professional Developer

Microsoft Most Valuable Professional

Month List