Into the Ring with knockout.js

Into the Ring with knockout.js

Tutorial Details
  • Topic: knockout.js, jQuery, templating
  • Difficulty: Beginner
  • Estimated Completion Time: 30 minutes
This entry is part 1 of 3 in the Into the Ring with Knockout.js Session
Next »

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:

<!DOCTYPE html>
<html>
    <head>
        <title>Knockout</title>
        <link rel="stylesheet" href="css/styles.css" />
    </head>
    <body>
        <script src="js/jquery-1.6.2.min.js"></script>
        <script src="js/jquery.tmpl.js"></script>
        <script src="js/knockout-1.2.1.js"></script>
        <script src="js/behavior.js"></script>
    </body>
</html>

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:

(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); } 
    }, {  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); } 
    }, {  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); } 
    }, {  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); } 
    }], viewModel = { people: ko.observableArray(model),
    }
  }; ko.applyBindings(viewModel);
    
})(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:

<div id="people" data-bind="template: { name: 'personTemplate', foreach: people }">
</div>
<script id="personTemplate" type="text/x-jquery-tmpl">
    <section class="person">
        <img src="../img/person.png" alt="${ name }" />
        <h1>${ name }</h1>
        <address>${ address }</address>
        <span class="tel">${ tel }</span>
        <a href="http://${ site }" title="Visit site">${ site }</a>
        <div class="tools">
            <button data-bind="click: deleteMe">Delete</button>
        </div>
    </section>
</script>

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:

<a href="#" title="Add new person" data-bind="click: showForm, visible: displayButton">Add person</a>
<fieldset data-bind="visible: displayForm">
    <div class="details">
        <label>Name: <input id="name" /></label>
        <label>Address: <input id="address" /></label>
        <label>Tel: <input id="tel" /></label>
        <label>Site: <input id="site" /></label>
    <div>
    <div class="img">
        <label>Picture: <input id="pic" type="file" /></label>
    </div>
    <div class="tools">
        <button data-bind="click: addPerson">Add</button>
        <button data-bind="click: hideForm">Cancel</button>
    </div>
</fieldset>

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):

displayButton: ko.observable(true), displayForm: ko.observable(false), showForm: function () { viewModel.displayForm(true).displayButton(false);
}, hideForm: function () { viewModel.displayForm(false).displayButton(true);
}, 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); }
    });
}

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:

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?

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Tim Visser

    First! :D

    • http://www.jebrini.net Mohd

      Congrats :D LOL
      Nice tut, I like it :)

  • http://blog.lastrose.com LastRose

    Definately a neat library, but I’m not sure I’m comfortable letting a client side language do so much heavy lifting, and I am concerned with how it will react when javascript is disabled.

    • Nico Schneider

      That’s how I *always* feel when I encounter ressources like this. It sure is awesome, and would be such a pleasure to use in projects. But how can you be sure that clients can actually *use* interfaces built upon client-side scripting? Should we require modern browsers, just like “real” software requires certain hardware and operating systems, and simply force users to update their browsers? If every browser would handle updating the Google-Chrome-Way, I’d sure be happy to use this.

      Thanks for the article, anyway. Good read.

      • Kapil

        Most of these libraries have features that work right down to ie 7 (i am not counting ie6 at all), though ie7 & 8 are quite slow for most js stuff. Only the latest html5 and css3 stuff is a problem for ie users as they don’t get free updates.
        So in my opinion, we can add necessary html5 stuff to our apps for most users and tell the ie guys to go and get a ‘real’ browser and come to our site.
        And as for ppl with js turned off, well, we can provide them a basic version of the app which works using GET & POST & COOKIES etc, but seriously, who keeps js turned off these days.

      • Tunox

        I completely agree with you as long as the implementation of such tools concerns only the front-end part. However, what we (front-end) designers tend to forget, is that there’s a whole world of (business) possibilities to implement such tools on back-end web apps, to improve user experience and work efficency. In business environments IT admins make the calls in regards of client-side configs. Hence browser support, JS on or off is an irrelevant matter.

  • http://michaelcrump.net Michael Crump

    Excellent Resource! I sent this out to all of my co-workers today. Great stuff!

  • Khalid Abuhakmeh

    LastRose and Nico have a point, but a small point. Where would the web be if we didn’t ask our users to move along with progress?

    Knockout looks like it depends on JQuery which has a good track record of working (even in IE). Ask your users to upgrade and you’ll be surprised how many will. So don’t let a good framework fade away just because you are worried about the last scragglers using IE 4.

    Great post.

  • Kapil

    Tunox ,

    Thats right, and most of the time those guys would rather go with statistics rather than taking a chance with heavy client side scripting, thats why i say learn a backend technology, its not that difficult (my choice was codeigniter, until phpfuel came out, but there are many, many options), and take that chance with your on idea for an app or a site

    • Kapil

      (contd..)As I read somewhere, javascript is the C of web, so lets start using it like that

  • Ed

    Although i love the idea of Client side doing the Heavy Lifting and making the Network only cares about delivering the data as fast as possible, i wonder what are the UI responsiveness of using such UI. And time to render the page. What happen to SEO when bot dont have JS ?

    • http://www.rcdmk.com RCDMK

      Well. We all know (or shold know) this can’t be indexed by the search engines, but the idea is to use this for a system UI interaction or maybe a restricted area or something similar.

      Of course you can build a blog entirelly on it, but if you want the content to be indexed, use the classic way.

      Nobody here is telling you to abbandon all SSS in all projects. Study the cases and make the right choice for each project, and sections of a project.

  • comentam

    Doesn’ work on behavior.js

    I’ve inspected with JavaScript Console in Google Chrome, and it said: “Uncaught SyntaxError: Unexpected token ; (in line 7)”

    Any idea?

    • crush

      there is an error in the behaviour.js example:
      the } in line 6 is wrong, just delete it :)

  • http://synfyre.net/ n1tr0b

    Now this is something to be tested

  • http://jayrajput.deviantart.com Jay Rajput
  • punkpette

    Can u upload a rar with ur example? Thx!

  • http://www.isshgraphicsart.com Ishaan k

    Thanks for the a new challenge you have uncovered in your post.

  • http://www.ruwebd.com.au/ Web design Melbourne

    useful stuff, I want to give it a try.

  • praveen

    Thanks for the tute. I have tested it. But didn’t work for me. I’m getting syntax error in Behavior.js.

    • marembu

      Thanks praveen

  • Juanprimal

    nice!!!

  • Krysh

    Where is the demo?

  • MacDonald

    This is a great framework with lot of possibilities.

  • marembu

    Brilliant!!!… Where is the demo?

  • Eastern Block

    Dan Wellman… you are always coming up with the coolest crap.

  • kosmos

    Adding framework specific data attributes into my html tags seems so UGLY to me.

  • http://www.a1media.ca/ Douglas Helmer

    Dan,

    For the life of me I couldn’t get this script to work even with the helpful tips from other readers such as “Crush” to remove the extra curly brace } in line 6 of behavior.js.

    Then, almost by accident, I moved all the javascript script library call lines below the other html display code and just above the closing body tag and bam! it all worked.

    I could’ve saved lots of time if the demo code that is mentioned in the article actually had been available as I would’ve seen from the git go what I was doing wrong.

    Complaints about the lack of a demo aside, Knockout is a really cool library! I intend to use it in several upcoming projects. Thanks for creating the article :)

  • http://bsatrom.github.com/Knockout.Unobtrusive/ SomeOne

    There is Unobtrusive add-on for those who don’t like having data attributes in the html (data-bind). Myself included.

    http://bsatrom.github.com/Knockout.Unobtrusive/
    http://userinexperience.com/?p=633

  • Alastair Hodgson

    For anyone struggling with the errors, i found the following to work:

    the syntac i the example code was all over the place…

    (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);
    }
    },
    {
    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);
    }
    },
    {
    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);
    }
    },
    {
    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);
    }
    }],
    viewModel = {
    people: ko.observableArray(model)
    }

    ko.applyBindings(viewModel);

    })(jQuery);

  • http://www.thoughtresults.com Saeed Neamati

    Thank you for introducing such a precious library. I visited knockout.js site, and I wanted to see who is/are behind it. But I found no about-us link anywhere.

    Who made this library? Can you mention that?

  • Philippe

    Amazing, man! Very nice Tuts!

  • https://github.com/dublinan Andre Dublin

    Great tutorial, I’ve been researching some JS options to speed up app development. BTW, there is a syntax error on the behavior file. There is an extra closing curly brace on line 7.

    It should be this

    (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); }
    }, { 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); }
    }, { 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); }
    }, { 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); }
    }], viewModel = { people: ko.observableArray(model),
    }
    ko.applyBindings(viewModel);

    })(jQuery);

  • http://www.netdream.com Stephen
  • Craig

    Keep in mind that jQuery tmpl is i permanent beta and is apparently not being maintained.
    http://blog.jquery.com/2011/04/16/official-plugins-a-change-in-the-roadmap/