Delivering a knockout.js punch
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.
- Add the selected country to a list of selectedCountries and display them in an unordered list.
- We are going to display a count of selected countries. (think of the faff involved in doing that with jQuery).
- We are going to update the available countries list and remove the selected item.
- We are going to make the selected item a link where the user can change there mind
- We are going to manage the two radio buttons to ensure they are set appropriately
