5 November 2012
Basic knowledge of JavaScript.
Intermediate
Knockout.js is a JavaScript library that enables you to declaratively bind elements against model data with two-way updates happening automatically between your UI and model. Getting started with Knockout is easy, and it integrates well with other libraries and technologies. Here is what you need to know to get started.
Developing a complex and dynamic data-driven web application can be a challenging task. Keeping a user interface in sync with the underlying data normally involves wiring up a number of event handlers to broker information between the various elements and the data whenever any actions are performed by a user or when new data is loaded.
An easy way to demonstrate the benefits of Knockout is to look at how you might create a simple editor with and without Knockout. Suppose the editor has a div to display an item's name and an input to edit that name:
<div id="itemName"></div>
<input type="text" id="itemNameEdit" />
Using jQuery, you could wire up interactions between an item's data and these elements like so:
var item = {
id: 88,
name: "Apple Pie"
};
$("#itemName").text(item.name);
$("#itemNameEdit").val(item.name).change(function() {
item.name = $(this).val();
$("#itemName").text(item.name);
});
There is nothing difficult about this code, but as an application needs to manage more and more interactions between the UI and data, this layer of code can continue to grow to the point that it is hard to maintain. Additionally, this type of code is tightly coupled with this specific markup, making reusability difficult.
Knockout enables you to use declarative bindings in your markup to connect your UI and data easily. In many cases, your elements will no longer need ids and classes, except when used in styling. Knockout's bindings are specified in data-bind attributes on individual elements:
<div data-bind="text: name"></div>
<input type="text" data-bind="value: name" />
Now that the elements have bindings specified, you will need to instruct Knockout to take your data and apply it to these elements using the ko.applyBindings function:
var item = {
id: 88,
name: "Apple Pie"
};
ko.applyBindings(item);
At this point, the div will display "Apple Pie" and edits made to the input will persist to the name property of your item. However, these updates will not cause your div to update automatically. To force these updates to notify interested parties, the name property needs to use one of Knockout's core structures, an observable. To create an observable, the name property needs to be wrapped in a call to ko.observable:
var item = {
id: 88,
name: ko.observable("Apple Pie")
};
ko.applyBindings(item);
Now, the div will be updated whenever the name observable changes. The data bindings automatically subscribe to any observables and are able to make appropriate updates to their DOM elements whenever they are notified of updates. You can also programmatically subscribe to an observable's changes, enablng you to execute additional logic such as pushing data back to the server.
Knockout enables you to use the MVVM (Model-View-ViewModel) pattern for structuring user interfaces. This pattern describes how you can manage complex functionality by keeping clean separation between several distinct layers:
The model represents your application's data. Knockout does not specifically handle brokering data to and from a back-end server and lets you decide the best way to implement this layer. The specific back-end technology is not important to Knockout either, as long as you can access your data from within your JavaScript code. Common examples of ways to do this include:
The view represents your markup and can really be considered a template to render against your view model. With a robust view model, the view should really have all that it needs to use simple declarative bindings against the concepts that the view model exposes. If you find that your bindings need to contain complex logic or expressions to achieve the correct result, then it is a good indication that the view model has not been structured in a manner that best supports the view.
The view model is the heart of your application. It is a code representation of your user interface that includes data and associated behaviors for manipulating that data. The goal of the view model is to provide an easy–to-digest structure for a view to bind against. It may include filtered, sorted, and manipulated versions of the model data, and potentially extra metadata or concepts that relate to operations in the user interface. For example, you may track whether an item is visible or editable, but not persist this information to a database.
In an ideal Knockout application, your view model contains no references to DOM elements, selectors, or any direct knowledge of the view that is binding against it. This separation provides some nice advantages:
In JavaScript, setting the value of a property does not inherently notify anyone that a change has been made. To support this need, Knockout creates several structures that are designed to track subscriptions and execute notifications when there are changes. When the underlying data changes, the bindings are then triggered and respond by making appropriate updates to the DOM elements.
Knockout's basic structure to facilitate dependency tracking and change notifications is called an observable. You can create an observable by calling ko.observable(). An observable is actually a function that internally caches the current value. To retrieve the value of an observable, you would call the function with no arguments. To set the value, you would pass a single argument to the observable with the new value:
this.name = ko.observable("Apple Pie");
alert(this.name()); //read the value
this.name("Pumpkin Pie"); //set the value
Knockout also provides computed observables as a way to maintain calculated values that stay up-to-date when their dependencies change:
this.formattedName = ko.computed(function() {
return this.id + " - " + this.name();
}, this);
When a computed observable is created, it will cache its current value and then only re-evaluate when one of its dependencies is updated. A computed observable determines these dependencies dynamically by tracking the values that it accesses during each re-evaluation. This means that you do not need to specify that values that it depends on have been updated, and the evaluation logic will only need to run when something that it depends on has changed.
Most applications deal with one or more collections of data. Knockout includes observableArrays to support arrays of data where the UI needs to know that items have been added or removed. A ko.observableArray is simply an observable that includes additional functions to handle normal array operations. When manipulating an observableArray, the underlying array is updated, and then any subscribers are notified that the array has changed:
this.items = ko.observableArray([
{ id: 1, name: "Apple Pie" },
{ id: 2, name: "Pumpkin Pie" },
{ id: 3, name: "Blueberry Torte" }
]);
this.items.push({ id: 4, "Strawberry Shortcake" });
It is important to note that an observableArray tracks changes to the items in the array and not the individual properties of each item. In the example above, if your application needs to respond to changes in an item's name, then the name property needs to be observable as well.
Bindings are the magic that connect your markup with your view model. Knockout includes bindings for common use cases and allows you to extend it with your own custom functionality. Tables 1-3 summarize the bindings that Knockout provides:
Table 1. Appearance and general bindings
Binding |
Description |
visible |
The visible binding allows you to show or hide an element based on a value passed to it. |
<div data-bind="visible: hasError">An error has occurred</div> |
|
Text |
The text binding populates the content of the element with the value passed to it. |
<div data-bind="text: message"></div> |
|
html |
The html binding populates the children of this element with the markup passed to it. |
<div data-bind="html: markup"></div> |
|
Css |
The css binding toggles one or more CSS classes on the element. |
<div data-bind="css: { error: hasError, required: isRequired }">content</div> |
|
style |
The style binding adds style values to the element. |
<div data-bind="style: { color: messageColor, backgroundColor: backColor }">content</div> |
|
Attr |
The attr binding sets the value of one or more attributes on the element. |
<div data-bind="attr: { title: itemDescription, id: itemId }">content</div> |
|
Table 2. Form field bindings
Binding |
Description |
Click |
The click binding executes a handler when the element is clicked. |
<button data-bind="click: addItem">Add Item</button> |
|
Event |
The event binding adds handlers to the element for the specified events. |
<div data-bind="event: { mouseover: showHelp, mouseout: hideHelp }">content</div> |
|
Submit |
The submit binding allows you to execute a handler when a form is submitted. |
<form data-bind="submit: saveData">…</form> |
|
Value |
The value binding enables two-way binding of the field’s value to a view model value. |
<input data-bind="value: name" /> |
|
Enable |
The enable binding controls whether the form element is enabled passed on the passed value. |
<input data-bind="enable: isEditable, value: name" /> |
|
Disable |
The disable binding provides the same functionality as the enable binding, but uses the opposite of the passed value. |
<input data-bind="disable: isReadOnly, value: name" /> |
|
Hasfocus |
The hasfocus binding tracks the focus state of the element and attempts to give the field focus when the value is set to true. |
<input data-bind="hasfocus: nameFocused, value: name" /> |
|
Checked |
The checkbox binding is used to bind against radio buttons or checkboxes. This can track true or false whether a checkbox is checked, the value of the currently selected radio button, or when bound against an array it can track all of the currently checked values. |
<input type="checkbox" data-bind="checked: isActive" /> |
|
Options |
The options binding is used to populate the options of a select element. It includes optionsText, optionsValue, and optionsCaption options that customize the way that the value is displayed and stored. |
<select data-bind="options: choices, value: name"></select> |
|
selectedOptions |
The selectedOptions binding tracks the currently selected items for a select element that allows multiple selections. |
<select data-bind="options: availableFilters, selectedOptions: selectedFilters" size="10" multiple="true"></select> |
|
Table 3. Control flow and templating bindings
Binding |
Description |
If |
The if binding determines if the element’s children are rendered and bound. It takes a copy of the child element to use as a “template” for handling when the bound value changes. |
<div data-bind="if: detailsLoaded"> <div data-bind="text: content"></div></div> |
|
ifnot |
The ifnot binding provides the same functionality as the if binding, but uses the opposite of the value passed to it to determine if the element’s should be rendered and bound. |
<div data-bind="ifnot: hideDetails"> <div data-bind="text: content"></div></div> |
|
with |
The with binding will bind the child elements using the value passed to it as the data context. It will not render the children if the value is null/undefined/false. It will also retain a copy of the child elements to use as a “template” for handling when the bound value changes. |
<div data-bind="with: details"> <div data-bind="text: title"></div> <div data-bind="text: content"></div></div> |
|
foreach |
The foreach binding will use the child elements as a “template” to repeat for each item in the array passed to it. |
<ul data-bind="foreach: items"> <li data-bind="text: name"></li></ul> |
|
template |
The template binding provides the underlying functionality for the if, ifnot, with, and foreach bindings, but also allows you to supply a named template that you can reuse multiple times. named template. |
<!—just passing a named template --><div data-bind="template: ‘itemsTmpl‘"></div><script id="itemTmpl" type="text/html"> <div data-bind="text: name"></div></script><!—controlling the data that is bound by the template --><div data-bind="template: { name: ‘itemTmpl‘, data: currentItem }"></div><!—iterating through an array of items --><div data-bind="template: { name: ‘itemTmpl‘, foreach: items }"></div> |
|
|
|
One of Knockout's guiding principles is to stick to its strengths and provide extensibility points for the areas outside of its core competencies. This gives you the flexibility to integrate Knockout with your preferred libraries and technologies. To get the most out of Knockout, you should become familiar with the various extensibility points that Knockout provides.
Knockout allows you to create your own bindings easily, and this is generally the most-used extensibility point. Whenever you find yourself needing to write code that touches both data and DOM elements in a way that the built-in bindings are not able to handle, then you will likely want to look at creating a custom binding.
All of the bindings (custom and built-in) live on the ko.bindingHandlers object. Each binding is an object that can choose to implement an init function that runs only the first time that the binding is executed, an update function that runs whenever one of its dependencies changes or both.
Here is an example of a custom binding that wraps the existing text binding and fades the text in whenever it changes:
ko.bindingHandlers.fadeInText = {
update: function(element, valueAccessor, allBindings, data, context) {
$(element).hide();
ko.bindingHandlers.text.update(element, valueAccessor);
$(element).fadeIn();
}
};
For more information on custom bindings, take a look at the custom bindings documentation and this custom bindings blog post that discusses common use cases for custom bindings.
Knockout also lets you extend the core structures that were discussed earlier. This creates interesting opportunities to create reusable functionality that can keep your view model code clean and concise.
All of the common functions for each of Knockout's core types live on their fn objects, such as ko.observable.fn. So, any functions that you add to ko.observable.fn will be available to all observables. For more information on the type hierarchy, see Adding custom functions using "fn" on the official Knockout site.
Here is an example where you create an editable extension that adds an editValue sub-observable to an observable. This lets you bind against a temporary value and commit it to your real observable by calling an accept function.
ko.observable.fn.editable = function() {
//create a sub-observable that we can bind against for editing
this.editValue = ko.observable(this());
//when accept is called, commit the temp value to our real value
this.accept = function() {
this(this.editValue());
}.bind(this);
//return the observable itself (this) to support chaining
return this;
};
You would then use this extension when you create an observable like:
this.name = ko.observable("Bob").editable();
In your markup, you could bind against it like:
<div data-bind="text: name"></div>
<input data-bind="value: name.editValue" />
<button data-bind="click: name.accept">Accept</button>
Extending the fn object of each of Knockout's core types is a useful way to create reusable concepts that allow you to extend Knockout's functionality. In addition to the fn object, Knockout also supports a similar approach called extenders that are described in the Knockout doucmentation under Using extenders to augment observables.
Knockout also includes some other notable extensibility points.
template binding. A base template engine is provided that can be extended to customize how the data and template are executed to determine the resulting markup.data-bind attributes with your own structure. An alternative binding provider that allows you to specify bindings in code and key into them from the markup is located at my knockout-classBindingProvider GitHub project and an accompanying Knockout 1.3 preview blog post describing how to create a binding provider.Now that you understand the basics of Knockout, where should you go from here? A good place to start is the online Knockout tutorials. These tutorials walk you through a variety of common scenarios, starting from the basics and moving into more advanced material. Additionally, full documentation is located at the official Knockout site and the source code is available from the Knockout GitHub project.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License. Permissions beyond the scope of this license, pertaining to the examples of code included within this work are available at Adobe.