February 2012

Volume 27 Number 02

Client Insight - Getting Started with Knockout

By John Papa | February 2012

John PapaData binding is one of the most popular features in development today, and the Knockout JavaScript library brings those features to HTML and JavaScript development. The simplicity of the declarative binding syntax and seamless integration with separation patterns such as Model-View-ViewModel (MVVM) make common push-and-pull plumbing tasks much simpler while making code easier to maintain and enhance. In this inaugural Client Insight column I’ll discuss the scenarios for which Knockout is ideal, explain how to get started with it and demonstrate how to use its fundamental features. The code examples, which you can download from msdn.com/magazine/msdnmag0212, demonstrate how to use declarative binding, create different types of binding objects and write data-centric JavaScript code that follows good separation patterns such as MVVM.

Getting Started

Knockout, developed by Steve Sanderson, is a small, open source JavaScript library with an MIT license. Knockoutjs.com maintains an updated list of the browsers that Knockout supports (currently it supports all major browsers including Internet Explorer 6+, Firefox 2+, Chrome, Opera and Safari). You need a few important resources to get started developing with Knockout. Start by getting the most recent version of Knockout (currently 2.0.0) from bit.ly/scmtAi and reference it in your project. If you’re using Visual Studio 2010, however, I highly recommend that you install and use the NuGet Package Manager Visual Studio extension to download Knockout (and any other libraries you might use) because it will manage versions and alert you when a new one is available. NuGet will download Knockout and put two JavaScript files in the scripts folder of your project. The minified file is recommended for production and follows the naming convention knockout-x.y.z.js where x.y.z is the major, minor and revision number. There’s also a knockout-x.y.x-debug.js file, which contains the Knockout source code in human-readable form. I recommend referencing this file when learning Knockout and when debugging.

Once you have the files, open your HTML page (or Razor file if you’re using ASP.NET MVC), create a script and reference the knockout library:

<script src="../scripts/knockout-2.0.0.js" type="text/javascript"></script>

No Bindings

Knockout is best explained by first examining how you would write code to push data from a source object to HTML elements without using Knockout (the relevant source code can be found in sample page 01-without-knockout.html in the downloadable code sample). I’ll then show how to accomplish the same thing using Knockout. I’ll start with some HTML target elements and push some values from a source object into these HTML elements:

<h2>Without Knockout</h2>
<span>Item number:</span><span id="guitarItemNumber"></span>
<br/>
<span>Guitar model:</span><input id="guitarModel"/>
<span>Sales price:</span><input  id="guitarSalesPrice"/>

If you have an object from which you want to push data into standard HTML elements, you could use jQuery:

$(document).ready(function () {
  var product = {
    itemNumber: "T314CE",
    model: "Taylor 314ce",
    salePrice: 1199.95
    }; 
    $("#guitarItemNumber").text(product.itemNumber);
    $("#guitarModel").val(product.model);
    $("#guitarSalesPrice").val(product.salePrice);
});

This code sample uses jQuery to locate the HTML elements with the corresponding IDs and sets their values to each appropriate object property.

There are three main points to notice in this code. First, the values are pushed from the source object into the HTML elements, thus requiring a line of code for each mapping from source value to target element. If there were many more properties (or if there were arrays and object graphs), the code could easily get unwieldy. Second, if the values in the source object change, the HTML elements won’t reflect that change unless the code to push the values was called again. Third, if the values change in the HTML elements (the target), the changes won’t be reflected in the underlying source object. Of course, all of this could be resolved with a lot of code, but I’ll try to resolve it using data binding.

The same HTML could be rewritten using Knockout:

<h2>With Knockout</h2>
<span Item number</span><span data-bind="text: itemNumber"></span>
<br/>
<span>Guitar model:</span><input data-bind="value: model"/>
<span>Sales price:</span><input data-bind="value: salePrice"/>

Notice the id attributes have been replaced with data-bind attributes. Once you call the applyBindings function, Knockout binds the given object (“product” in this example) to the page. This sets the data context for the page to the product object, which means that the target elements can then identify the property of that data context to which they want to bind:

ko.applyBindings(product);

The values in the source object will be pushed to the target elements in this page. (All of Knockout’s functions exist within its own namespace: ko.) The linkage between the target elements and the source object is defined by the data-bind attribute. In the preceding example, Knockout sees the data-bind attribute for the first span tag is identifying that its text value should be bound to the itemNumber property. Knockout then pushes the value for the product.itemNumber property to the element.

You can see how using Knockout could easily reduce code. As the number of properties and elements increase, the only JavaScript code required is the applyBindings function. However, this doesn’t yet solve all of the problems. This example doesn’t yet update the HTML targets when the source changes, nor does the source object update when the HTML targets change. For this, we need observables.

Observables

Knockout adds dependency tracking through observables, which are objects that can notify listeners when underlying values have changed (this is similar to the concept of the INotifyPropertyChanged interface in XAML technology). Knockout implements observable properties by wrapping object properties with a custom function named observable. For example, instead of setting a property in an object like so:

var product = { 
  model: "Taylor 314ce" 
}

you can define the property to be an observable property using Knockout:

var product = { 
  model: ko.observable("Taylor 314ce") 
}

Once the properties are defined as observables, the data binding really takes shape. The JavaScript code shown in Figure 1 demonstrates two objects that Knockout binds to HTML elements. The first object (data.product1) defines its properties using a simple object literal while the second object (data.product2) defines the properties as Knockout observables.

Figure 1 With and Without Observables

$(document).ready(function () {
  var data = {
    product1: {
      id: 1002,
      itemNumber: "T110",
      model: "Taylor 110",
      salePrice: 699.75
    },
    product2: {
      id: ko.observable(1001),
      itemNumber: ko.observable("T314CE"),
      model: ko.observable("Taylor 314ce"),
      salePrice: ko.observable(1199.95)
    }
  };
 
  ko.applyBindings(data);
});

The HTML for this sample, shown in Figure 2, shows four sets of element bindings. The first and second div tags contain HTML elements bound to non-observable properties. When the values in the first div are changed, notice that nothing else changes. The third and fourth div tags contain the HTML elements bound to observable properties. Notice that when the values are changed in the third div, the elements in the fourth div are updated. You can try this demo out using example 02-observable.html.

Figure 2 Binding to Observable and Non-Observables

<div>
  <h2>Object Literal</h2>
  <span>Item number</span><span data-bind="text: product1.itemNumber"></span>
  <br/>
  <span>Guitar model:</span><input data-bind="value: product1.model"/>
  <span>Sales price:</span><input data-bind="value: product1.salePrice"/>
</div>
<div>
  <h2>Underlying Source Object for Object Literal</h2>
  <span>Item number</span><span data-bind="text: product1.itemNumber"></span>
  <br/>
  <span>Guitar model:</span><span data-bind="text: product1.model"></span>
  <span>Sales price:</span><span data-bind="text: product1.salePrice"></span>
</div>
<div>
  <h2>Observables</h2>
    <span>Item number</span><span data-bind="text: product2.itemNumber"></span>
  <br/>
    <span>Guitar model:</span><input data-bind="value: product2.model"/>
    <span>Sales price:</span><input data-bind="value: product2.salePrice"/>
</div>
<div>
  <h2>Underlying Source Object for Observable Object</h2>
  <span>Item number</span><span data-bind="text: product2.itemNumber"></span>
  <br/>
    <span>Guitar model:</span><span data-bind="text: product2.model"></span>
    <span>Sales price:</span><span data-bind="text: product2.salePrice"></span>
</div>

(Note: Knockout doesn’t require you to use observable properties. If you want DOM elements to receive values once but then not be updated when the values in the source object change, simple objects will suffice. However, if you want your source object and target DOM elements to stay in sync—two-way binding—then you’ll want to consider using observable properties.)

Built-in Bindings

The examples thus far have demonstrated how to bind to the Knockout’s text and value bindings. Knockout has many built-in bindings that make it easier to bind object properties to target DOM elements. For example, when Knockout sees a text binding, it will set the innerText property (using Internet Explorer) or the equivalent property in other browsers. When the text binding is used, any previous text will be overwritten. While there are many built-in bindings, some of the most common for displaying can be found in Figure 3. The Knockout documentation online contains a complete list in the left navigation pane (bit.ly/ajRyPj).

Figure 3 Common Knockout Bindings

Example Scenario
text: model Binds the property (model) to the text value for the target element. Often used for read-only elements such as spans.
visible: isInStock Binds the value property (isInStock) to the visibility of the target element. The property value will evaluate to true or false.
value: price Binds the value of the property (price) to the target element. Often used with input, select and textarea elements.
css: className Binds the value of the property (className) to the target element. Often used to set or toggle css class names for DOM elements.
checked: isInCart Binds the value of the property (isInCart) to the target element. Used for checkbox elements.
click: saveData Adds an event handler for the bound JavaScript function (saveData) when the user clicks the DOM element. Works on any DOM element but is often used for button, input and a elements.
attr: {src: photoUrl, alt: name} Binds any specified attribute for the DOM element to the source object. Often used when the other built-in bindings don’t cover the scenario, such as with the src attribute of an img tag.

ObservableArrays

Now that the previous examples got your feet wet with Knockout, it’s time to move on to a more practical yet still fundamental example with hierarchical data. Knockout supports many types of bindings, including binding to simple properties (as seen in the previous examples), binding to JavaScript arrays, computed bindings and custom bindings (which I’ll look at in a future article on Knockout). The next example demonstrates how to bind an array of product objects using Knockout to a list (shown in Figure 4).

Binding to an Observable Array
Figure 4 Binding to an Observable Array

When dealing with object graphs and data binding, it’s helpful to encapsulate all of the data and functions the page requires into a single object. This is often referred to as a ViewModel from the MVVM pattern. In this example the View is the HTML page and its DOM elements. The Model is the array of products. The ViewModel glues the Model to the View; the glue it uses is Knockout.

The array of products is set up using the observableArray function. This is similar to the ObservableCollection in XAML technologies. Because the products property is an observableArray, every time an item is added to or removed from the array, the target elements will be notified and the item will be added or removed from the DOM, as shown here:

var showroomViewModel = {
  products: ko.observableArray()
};

The showroomViewModel is the root object that will be data bound to the target elements. It contains a list of products, which come in from a data service as JSON. The function that loads the product list is the showroomViewModel.load function, which appears in Figure 5 along with the rest of the JavaScript that sets up the showroomViewModel object (you’ll find the complete source and sample data for this example in 03-observableArrays.html). The load function loops through the sample product data and uses the Product function to create the new product objects before pushing them into the observableArray.

Figure 5 Defining the Data to Bind

var photoPath = "/images/";
function Product () {
  this.id = ko.observable();
  this.salePrice = ko.observable();
  this.listPrice = ko.observable();
  this.rating = ko.observable();
  this.photo = ko.observable();
  this.itemNumber = ko.observable();
  this.description = ko.observable();
  this.photoUrl = ko.computed(function () {
    return photoPath + this.photo();
  }, this);
};
var showroomViewModel = {
  products: ko.observableArray()
};
showroomViewModel.load = function () {
  this.products([]); // reset
  $.each(data.Products, function (i, p) {
    showroomViewModel.products.push(new Product()
      .id(p.Id)
      .salePrice(p.SalePrice)
      .listPrice(p.ListPrice)
      .rating(p.Rating)
      .photo(p.Photo)
      .itemNumber(p.ItemNumber)
      .description(p.Description)
      );
  });
};
ko.applyBindings(showroomViewModel);

Although a product’s properties are all defined using observables, they don’t necessarily need to be observable. For example, these could be plain properties if they’re read-only, and if—when the source changes—either the target isn’t expected to be updated or the entire container is expected to be refreshed. However, if the properties need to be refreshed when the source changes or is edited in the DOM, then observable is the right choice.

The Product function defines all of its properties as Knockout observables (except photoUrl). When Knockout binds the data, the products array property can be accessed, making it easy to use standard functions such as length to show how many items are currently data bound:

<span data-bind="text: products().length"></span>

Control-of-Flow Bindings

The array of products can then be data bound to a DOM element, where it can be used as an anonymous template for displaying the list. The following HTML shows that the ul tag uses the foreach control-of-flow binding to bind to the products:

<ul data-bind="foreach:products">
  <li class="guitarListCompact">
    <div class="photoContainer">
      <img data-bind="visible: photoUrl, attr: { src: photoUrl }" 
        class="photoThumbnail"></img>
    </div>
    <div data-bind="text: salePrice"></div>
  </li>
</ul>

The elements inside the ul tag will be used to template each product. Inside the foreach, the data context also changes from the root object showroomViewModel to each individual product. This is why the inner DOM elements can bind to the photoUrl and salePrice properties of a product directly.

There are four main control-of-flow bindings: foreach, if, ifnot and with. These control bindings allow you to declaratively define the control of flow logic without creating a named template. When the if control-of-flow binding is followed by a condition that is truthy or the ifnot binding is followed by a condition that evaluates to false, the contents inside of its block are bound and displayed, as shown here:

<div data-bind="if:onSale">
  <span data-bind="text: salePrice"></span>
</div>

The with binding changes the data context to whatever object you specify. This is especially useful when diving into object graphs with multiple parent/child relationships or distinct ViewModels within a page. For example, if there’s a sale ViewModel object that’s bound to the page and it has child objects customer and salesPerson, the with binding could be used to make the bindings more readable and easier to maintain, as shown here:

<div data-bind="with:customer">
  <span data-bind="text: name"></span><br/>
  <span data-bind="text: orderTotal"></span>
</div>
<div data-bind="with:salesPerson">
  <span data-bind="text: employeeNum"></span><br/>
  <span data-bind="text: name"></span>
</div>

Computed Observables

You might have noticed that the Product function defined the photoUrl as a special type of computed property. ko.computed defines a binding function that evaluates the value for the data-binding operation. The computed property automatically updates when any of the observables it depends on for its evaluation change. This is especially useful when the value isn’t clearly represented in a concrete value in the source object. Another common example besides creating a URL is creating a fullName property out of  firstName and lastName properties.

(Note: Previous versions of Knockout referred to computed properties as dependentObservable. Both still work in Knockout 2.0.0, but I recommend using the newer computed function.)

A computed property accepts a function for evaluating the value and the object that will represent the object to which you’re attaching the binding. The second parameter is important because JavaScript object literals don’t have a way of referring to themselves. In Figure 5 the this keyword (which represents the showroomViewModel) is passed in so the dependent observable’s function can use it to get the photo property. Without this being passed in, the photo function would be undefined and the evaluation would fail to produce the expected URL:

this.photoUrl = ko.computed(function () {
  return photoPath +  photo();  // photo() will be undefined
});

Understanding Fundamental Binding Properties

This column got you started with data binding using the Knockout JavaScript library. The most important aspect of Knockout is to understand the fundamental binding properties: observable, observableArray and computed. With these observables you can create robust HTML apps using solid separation patterns. I also covered the more common built-in bindings types and demonstrated the control-of-flow bindings. However, Knockout supports much more functionality. Next time, I’ll explore built-in bindings in more detail.


John Papa is a former evangelist for Microsoft on the Silverlight and Windows 8 teams, where he hosted the popular Silverlight TV show. He has presented globally at keynotes and sessions for the BUILD, MIX, PDC, Tech•Ed, Visual Studio Live! and DevConnections events. Papa is also a columnist for Visual Studio Magazine (Papa’s Perspective) and author of training videos with Pluralsight. Follow him on Twitter at twitter.com/john_papa.

Thanks to the following technical expert for reviewing this article: Steve Sanderson