Into the Ring with knockout.js

This post is part of a series called Into the Ring with Knockout.js.
Into the Ring with knockout.js: Part 2 - the Rematch

In the red corner, weighing in at just 29Kb (uncompressed), is knockout.js; a pure JavaScript library that simplifies the creation of dynamic user interfaces. Knockout is library agnostic, so it can easily be used with any of the most popular JavaScript libraries already available, but it works particularly well with jQuery, and uses jQuery.tmpl as its default templating engine.

Knockout is not meant to be a replacement for jQuery.

Knockout is not meant to be a replacement for jQuery; jQuery is hugely popular, as you all know I'm a huge fan of it myself, and it's very good at what it does. But it's difficult to create complex user interfaces using jQuery alone; the bigger the application behind the interface, and the more the user can interact with it, the harder it gets to keep some semblance of order. Event handlers abound, and you quickly end up with literally hundreds of lines of code.

It's perfectly possible to build complex and highly dynamic UIs with jQuery alone, but does your project's budget have the time required to write and debug 800+ lines of code? What about in 6 months time when something needs to change, or be added? This is where knockout comes in.

Overview

In this tutorial we'll build a simple interface that displays a list of contacts and then allows the visitor to interact with the UI to change how the data is displayed, such as filtering the list, or sorting it. We'll use knockout as a layer between our data and the page to simplify the creation and management or our UI.

Round 1 - Getting Started

Knockout uses a View-model-view model architecture. The visible list of contacts we use in this example and the elements on the page that they consist of, can be thought of as a view. The data that is displayed on the page is the model. The view model is a representation of the current state of the UI, a combination of the data and the view which also contains the behavior used to interact with the model and update the view.

Let's get started by creating the folder structure we'll need and the basic page that we'll be working with. Create a new folder called knockout somewhere on your system, then within this folder create three new folders called css, img and js. The css folder will be used to hold the simple style sheet we'll use, and the img folder the single image. The js folder will contain the script file we create, as well as the libraries we're dependent on. Initially this folder will need to contain the following files:

• jquery.tmpl.js
• jquery-1.6.2.js
• knockout-1.2.1.js

Now, in your text editor, create the following basic page:

 1   2   3   4  Knockout  5   6   7   8   9   10   11   12   13  

Save this page as index.html in the root knockout folder. So far, there's nothing note-worthy here other than the use of HTML5. Although knockout.js is compatible with earlier versions of HTML, the attributes we'll be adding to our elements are not part of the standard HTML 4.01 standard and the page will therefore be invalid. This is not the case with HTML5, which defines data-* attributes for embedding custom data.

We also use a basic style sheet for this example, but it's only used for this particular example and is completely arbitrary. As this isn't a CSS tutorial, I'll avoid showing it here, but if you're curious, take a look at the file in the demo.

The Behavior File

Next, we can create our behavior file; in a new page in your text editor add the following code:

 1 (function ($) { var model = [{ name: "John", address: "1, a road, a town, a county, a postcode", tel: "1234567890", site: "www.aurl.com", pic: "/img/john.jpg", deleteMe: function () { viewModel.people.remove(this); }  2  }, { name: "Jane", address: "2, a street, a city, a county, a postcode", tel: "1234567890", site: "www.aurl.com", pic: "/img/jane.jpg", deleteMe: function () { viewModel.people.remove(this); }  3  }, { name: "Fred", address: "3, an avenue, a village, a county, a postcode", tel: "1234567890", site: "www.aurl.com", pic: "/img/fred.jpg", deleteMe: function () { viewModel.people.remove(this); }  4  }, { name: "Freda", address: "4, a street, a suburb, a county, a postcode", tel: "1234567890", site: "www.aurl.com", pic: "/img/jane.jpg", deleteMe: function () { viewModel.people.remove(this); }  5  }], viewModel = { people: ko.observableArray(model),  6  }  7  }; ko.applyBindings(viewModel);  8   9 })(jQuery);  Save this file as behavior.js in the js folder. We start out by defining a self-invoking function, which we pass jQuery into in order to alias the $ character.

We then define the model that we'll use. In this example it's a local array, but we could get exactly the same format of data from a web service easily enough. Our array contains a series of people objects, which correspond to individual entries in a contacts database. Mostly, our data consists of simple strings, but each object also contains a deleteMe method, which is used to remove the object from the viewModel.

Remember, the viewModel refers to the current state of the UI. It's an object, and the first item we add to it is our array containing the people objects. We use the knockout ko.observableArray() method to add our array to the viewModel object. Observables are a fundamental aspect of knockout.js; we are instructing knockout to allow other entities to observe these items and react when they change.

This is all our view Model contains at the moment, although we've left a hanging comma after the people property's value for when we add more properties.

After the object object, we use the ko.applyBindings() method to apply any bindings we've created and begin managing the viewModel. At this point in the example, we haven't yet added any bindings. To create bindings between our view and viewModel, we need to add some more HTML.

Round 2 - Creating a View

Knockout works brilliantly with jQuery templating.

We now have our model and a simple viewModel in place. The next thing we should do is display the data from the viewModel on the page. Knockout works brilliantly with jQuery templating. This allows us to use the tmpl plugin to build the required HTML. Add the following code to the <body> element of the page, directly before the <script> elements:

 1 
 2 
 3  

We first add an empty <div> element with an id - mostly for styling purposes. This element also has a special attribute - data-bind. This attribute tells knockout that the element stores its data in the viewModel. When we called ko.applyBindings() in our JS, this is a binding that is applied. In this case, we use the template binding which allows us to specify the name of a template that we'd like to use in a configuration object passed to the binding.

We also use the foreach property in this configuration object and specify the name of our people observableArray as the source of our data. We could use the standard tmpl syntax, {{each}}, to iterate over our people data, but it is more efficient to use knockout's syntax instead. Because our people data is contained within an observable array, knockout will monitor the array for changes, and, when any occur, it will automatically update any templates that are displaying the data. If we use tmpl syntax, our entire template will be re-rendered each time the data changes, but when we use knockout's foreach property, only the single instance corresponding to the item that has changed is re-rendered.

Following the container <div> we then define our template. This is done in the same way as a normal tmpl template. Within the template, we specify the elements that we would like repeated for each object in our data source. We have a <section> element as a container, followed by an appropriate element for each item within person object. One thing to note is that we can supply bindings in our template code. We add a data-bind attribute to a delete button; this time we use the click binding and specify the name of the person found within each person object.

When we run the page in a browser, we should find that our page contains the data from our viewModel, nicely rendered using our template:

So that's pretty cool right? But it's not that dissimilar to using the tmpl plugin.

The really cool thing is that not only is the view updated accordingly when the viewModel changes, the viewModel is also updated when the view changes. So if we click one of the delete buttons on our page, the people array will also have the corresponding person object removed from it!

The original array which we passed into the ko.observable() method isn't actually updated, but normally, we'd probably get our data from an AJAX request instead of hard-coding it into the page, so all we'd need to do is resubmit the data, with the person removed.

Round 3 - Adding new Data

We've got the ability to remove a person object; next, we can add the ability to add a new person into our dataModel; Update the container <div> we added to the page earlier so that it contains the following new elements:

 1 Add person  2 
 3 
 4   5   6   7   8 
 9 
 10   11 
 12 
 13   14   15 
 16 


The first new element we add is an <a> tag, which is used to open up the form that will accept the new data. This is similar to how we'd do it in a regular jQuery implementation, except that we'd also have to add an event handler to listen for clicks on the element, and do things such as stopping the event. With knockout, we don't have to worry about any of that. All we need to do is specify the name of a method within our viewModel, which we'd like to execute whenever the element is clicked. Knockout will attach the handler and stop the link being followed for us.

As you can see, we can specify multiple bindings on an element. Our <a> element also uses the visible binding. Again, we specify a property of our viewModel, except that this time, it isn't a function but a simple variable containing a boolean; you'll see how this works when we come to add the JS for our new functionality in a moment.

After the link, we also add a <fieldset> containing labels and inputs that we can use to add the relevant data to make a new object in our people array. At the end of our new HTML, we add two new <button> elements; both of these have click bindings added to them. The first links to the addPerson method, the second to the hideForm method. The image uploading doesn't actually work in this example, it's only there for show.

Now let's take a look at the new JavaScript we need; add the following code directly after the people property of our viewModel (we left a hanging comma ready to add these new properties and methods):

 1 displayButton: ko.observable(true), displayForm: ko.observable(false), showForm: function () { viewModel.displayForm(true).displayButton(false);  2 }, hideForm: function () { viewModel.displayForm(false).displayButton(true);  3 }, addPerson: function () { viewModel.displayForm(false).displayButton(true).people.push({ name: $("#name").val(), address:$("#address").val(), tel: $("#tel").val(), site:$("#site").val(), pic: "", deleteMe: function () { viewModel.people.remove(this); }  4  });  5 } 

The first property is displayButton, which is an observable property (its value may be observed) by other entities. The entity that observes its value is our <a> element in the view. We initially set it to true, so when the page loads (or rather when the applyBindings() method is called), the link will be visible.

The next property is called displayForm, which is also an observable, except, this time, we set it to false, so the element in our view which is observing it (the fieldset) will initially be hidden.

We then add two methods: showForm() and hideForm(). These two simple methods are used to, obviously, show or hide the form respectively, and to do that, all they need to do is set the displayForm observable property to true or false. Because the value is being observed, any time their value changes, our view will be updated automatically.

We also adjust the showButton property whenever the state of the form changes. If the fieldset is visible, we hide the link, and if we hide the fieldset, the button is made visible again. As you can see, knockout supports chaining, which makes updating multiple properties in our viewModel extremely easy. The view should appear like this when the form is visible:

The last method we add is the addPerson() method, which is used to update our viewModel with the details of the new person. All we do in this method is hide the form and show the button, and create an object literal containing the values entered into the text fields and then push this object into our people array.

To retrieve the updated people array from our viewModel, we can use knockout's built in JSON serialiser to write the observable array to a JSON object. We would normally do this in order to pass the data back to the server, but, to test it out, we could add this line of code to the end of the addPerson() method:

 1 console.log(ko.toJSON(viewModel.people)); 

The ko.toJSON() method helpfully generates a JSON object containing the current contents of the people array, which we can see in Firebug (other DOM explorers are available):

Post Fight Review

In this tutorial, we covered two major aspects of knockout.js - declarative bindings and observables.

The bindings are applied in our HTML and specify properties and arrays of data whose values should be observed for changes. When these values do change, the elements in the view that are observing them will be updated automatically, either by applying a new iteration of a template, or by showing or hiding an element, as in this example.

There are other bindings we can use as well to perform different actions when the view is interacted with, or the data in the viewModel is updated.

Knockout.js is an extremely helpful layer that sits between our UI's interface and its underlying data, and manages interactions and state changes for us. It does so much work for us, although we've really only scratched the surface of what it is capable of in this basic example. What are your thoughts on knockout.js?