Delivering a knockout.js punch

Tags: knockout.js javascript html UI MVVM

The code: knockoutExample.zip

This post explains how knockout.js can greatly simplfy the creation of some fairly complex UI interactions.

 

I was recently working on a web page that had a file upload form and a fairly complex country selector. As I am sure you know, if you post back on the form after selecting a file to upload, the file uploader will be blanked. This by design and is to ensure security. The problem was the country selector, which was also required on the form, needed to post back. I eventually solved the problem with some fairly nasty jQuery code. Since then, however, I have discovered knockout.js

Knockout.js is all about 'simplifying dynamic javascript UIs by applying the Model-View-View Model (MVVM) pattern'. For my first post I thought I would show you my first faultering steps towards being a knockout.js guru (a long way to go I feel).

 

The spec

The user of the form needs to be able to specify if the file being uploaded should be available to anyone from anywhere or only available to people from particular countries. For simplicity, this screen shot of the form is just of the country selector.

 

To the code!


First download the latest js files from the knockout website. Its then just a simple process of referencing the files in your page. For simplicity here I put the js files in the same folder as my html page.

 

 

<head>

<title>Knockout example</title>

<script type='text/javascript' src='./jquery-1.6.1.min.js'></script>

<script type='text/javascript' src='./jquery.tmpl.js'></script>

<script type='text/javascript' src='./knockout-1.2.1.js'></script>

</head>

 

The next stage was to build up the html of the page. At the start it looked something like this:

 

 

<body>

<h1>Country selector</h1>

    <ul>

        <li>

            <span >

                <label> All countries <input type="radio" name="countryOptionGroup" value="allCountries"/>/label>

            </span>

        </li>

 

        <li>

            <span class="filter">

                <input type="radio" name="countryOptionGroup" value="specificCountries" />

            </span>

            <label>specific countries</label>

        </li>

    </ul>

 

    <select></select>

 

    <input type="button" value="Add" title="Add a country"/>

 

    <h2>Selected ()</h2>

 

    <ul></ul>

</body>

 

 

 

Given this shell we need to add the knockout magic to make it all work. In essence we build a javascript object model that describes the data and the methods we can perform and 'bind' it to the UI. Starting with the selector we would need to bind a country list to it. 

 

The start of the View-Model

 

 

<script type='text/javascript'>

/*<![CDATA[*/

 

        // First define a thing object called countryName with a property of 'name'

        function country(countryName){

            return{

                name: ko.observable(countryName)

            }

        };

 

// Next define an object called countrySelecter to house the main properties, data and methods

var countrySelecter = {

// Properties

selectedOptionValue: ko.observable("allCountries"),

availableCountries: ko.observableArray([new country("Afghanistan"), new country("Albania"), new country("Algeria"), new country("American Samoa"), new country("Andorra"), new country("Angola"), new country("Anguilla"), new country("Antigua and Barbuda"), new country("Argentina"), new country("Armenia"), new country("Aruba"), new country("Australia")]),

selectedCountry: ko.observable(),

selectedCountries: ko.observableArray()

        };

 

// This line applies the bindings 

ko.applyBindings(countrySelecter); // This makes Knockout get to work

 

/*]]>*/

</script>

 

Databinding

 

At this point we have a set of object, properties and methods which are all tied into the binding mechanism. For this example I will bind the initial country list to the 'select' we have on the page. To do this edit the 'select' html in the following way:

 

<select data-bind="options: availableCountries, optionsText: 'name', value: selectedCountry"></select> 

 

The magic here is in the data-bind section. When you closely at it you can see how the mechanics of knockout.js work. The options are bound to the avaialbleCountries list on the countrySelecter object. The selected text is bound to the 'name' property of the countryName object. Finaly the value part is bound to the selectedCountry property. All very elegant. Form that point you can build up the view-model to create the interaction required. 

 

Adding methods

 

Now that we have a select populated with countries we need to act when the add button is pressed. For this example we are going to do the following things at that point.

 

  1. Add the selected country to a list of selectedCountries and display them in an unordered list.
  2. We are going to display a count of selected countries. (think of the faff involved in doing that with jQuery).
  3. We are going to update the available countries list and remove the selected item.
  4. We are going to make the selected item a link where the user can change there mind
  5. We are going to manage the two radio buttons to ensure they are set appropriately
So the next thing we need is a method for taking care of the 'Add' process. Here it is:
        countrySelecter.addCountry = function () {
            this.selectedOptionValue("specificCountries");
            this.selectedCountries.push(this.selectedCountry());
            this.availableCountries.remove(this.selectedCountry());
            this.selectedCountries.sort();
        }
By using the '.' at the end of the object name I am able to attach a method to my countrySelecter object. In this method we see how to play with arrays. Notice that there is no messing around counting items in any lists and finding elements in the DOM to update. All that is taken care of through binding. So to add the count functionality all I needed to do was to update my HTML like this:
<h2>Selected (<span data-bind="text: selectedCountries().length"></span>)</h2>
Knockout manages the bindings  for me and ensures all the interested elements are updated as the view-model is updated. So to ensure the count is up displayed all I need do is tell the span to bind its text property to the length of the selectedCountries array. The updates are handled for me! Doing the same thing in jQuery is clearly possible but requires a lot more effort and debugging.
jQuery templates and binding
To show off further binding I used a jQuery template to display the selected list. To add it in replace the <ul></ul> in the original HTML with the following:
<ul data-bind="template: {name:'selectedCountriesTemplate', foreach: selectedCountries}"></ul>
<script id='selectedCountriesTemplate' type='text/html'>
        <li>
             <a href="" data-bind="click: remove"> ${ name } </a>
        </li>
</script>
Here we have the binding section in the <ul> and the template in the script. This illustrates how to bind to methods, in this case the 'remove' function. We also see the handy 'foreach' iterator in the binding section of the <ul>. There is loads more to learn about templates but on of the really smart things is that the binding mechanism only updates the bits that change leading to a highly performant page capable of handling large data sets. I have used another way of adding methods to view-model objects to illustrate how the remove function works. I've added it directly into the countryName object like follows:
function country(countryName){
            return{
                name: ko.observable(countryName),
                remove: function(){
                    countrySelecter.selectedCountries.remove(this);
                    countrySelecter.availableCountries.push(this);
                    countrySelecter.availableCountries.sort();
                    if(countrySelecter.selectedCountries().length == 0) {
                        countrySelecter.selectedOptionValue("allCountries");
                    }
                }
            }
        };
Conclusion
The knockout.js library is well worth a second look. I have only scratched the surface in the mini example but it appears to be a clean powerful and quick way to put together some intricate UI interactions. Do checkout the knockout.js website and have a look at the samples.  

 

Add a Comment