In this section, we will explore some of the key features of Knockout. It is important to understand these features and their basic syntax before we dive into working examples.
Knockout provides a way to link the model and view model with the view using a declarative binding mechanism. The bindings are declared in HTML. The following is an example of a simple text binding:
The phone number for <span data-bind="text: name></span> is 0000111
Let's explore the data binding syntax. The bindings are declared using the data-bind attribute on an HTML element. The value of this attribute has two elements, which are separated by a colon. The two elements are name and a value.
The name specifies the type of binding. This should match a registered binding handler. A binding handler is an object that contains the code to bind the HTML element to our model. Knockout provides a number of useful binding handlers. A custom binding handler can be created and registered with Knockout if none of the out-of-the-box handlers meet your specific requirements. In most cases, the out-of-the-box handlers will do the job.
Tip
Knockout will ignore the binding without any error if the name does not match any of the registered binding handlers. Check the name if the binding does not appear to be working!
The value can be an attribute from the model or any valid JavaScript expression. In the preceding example for contact, we used a text binding with the value, and the name. The value in this case comes from the model.
Here is an example of a binding using a JavaScript expression:
The phone number for <span data-bind="text: retrieveContactName()></span> is 0000111
In this example, the text value is evaluated by calling the retrieveContactName
JavaScript function.
Note
Knockout will throw an error and stop processing the bindings if the value is an invalid expression or if it references an undefined variable.
You can include multiple bindings in the data-bind
attribute, with each binding separated by a comma. Adding a visible binding to our weather forecast example will make it look similar to this:
The phone number for <span data-bind="text: name, css: favourite"></span> is 0000111
In the preceding example, the text for the span
element will come from the name
attribute in our model. The css
binding will determine the CSS class to be applied, based on the favourite
attribute in our model for the span
element.
Tip
You can include any number of spaces, tabs, or newlines in your binding syntax. Use this to arrange your bindings to make them more readable!
In more advance usage, the binding can also be a parameter for another binding. Here is an example in which the template binding takes the foreach
binding as a parameter:
<tbody data-bind="template: {name: 'contact-template', foreach: contacts}">
As mentioned earlier, Knockout provides a number of very useful binding handlers that come out of the box. Knockout documentation divides these binding handlers in to three categories:
Controlling text and appearance: As the name suggests, these binding handlers control the text and the styling of the UI elements. Examples of binding handlers in this category include
text
andcss
. We used these bindings as examples earlier in this section.Flow control: These binding handlers provide control structures such as loops and conditions. The
foreach
andif
binding handlers fall under this category. We will explore these bindings in more detail in the coming sections.Working with form fields: Capturing data with forms is one of the most basic requirements in web applications. Binding handlers in this category provide the functionality to work with form fields. Some of the examples include
click
,value
, andsubmit
binding handlers. We will learn more about bindings in this category in Chapter 3, Creating an Online Customer Registration Form and Chapter 4, Adding Validation to the Customer Registration Form.
Automatic UI refresh is a very useful feature of Knockout. This feature is based on the concept of two-way binding between the view and view model. Whenever the data in the model changes, it is reflected in the UI. When the input fields in the UI change, it updates the underlying data.
This feature reduces the amount of code and complexity by many folds. Those who are accustomed to writing event handlers in JavaScript to connect data with UI fields and vice versa would surely appreciate this feature. Implementing this in jQuery is definitely easier than developing this in pure JavaScript, but it does not compare with Knockout.
The examples in the previous section for data binding and view produces a one-way binding between the UI and model. Updating the value in the UI field will update the data in the model. To make this binding work both ways, you have to declare the attributes in your model as observables.
Observables are objects that notify their subscribers of any change. Let's apply observables to our contact model:
var contact = { id: ko.observable(1), name: ko.observable('John'), phoneNumber: ko.observable('00001111') };
By declaring the attributes in your model as observable object, you have activated the two-way binding. You do not have to make any change to the data bindings or view.
Since observables are functions, you can no longer access the attribute in the standard way. To read the value of our name observable, we execute it as a function like this:
contact.name();
To change the value of our name observable to Mary
, simply pass the new value as an argument to the name function as follows:
contact.name('Mary');
We mentioned that observables notify their subscribers of any change. When we use observables with data binding, the binding registers itself to be notified when the observable changes value. When the value of the observable changes, the binding automatically updates the UI element.
You can also explicitly subscribe to observables, have observables with values that are computed, or even delay change notification. We will learn more about these later on in the book.
Dependency tracking is one of the most exciting features of Knockout. Dependency tracking is based on observables and their subscribers. When Knockout runs for the first time, it evaluates the initial value of each observable and sets up the subscriptions. The subscribers get notified when the observable gets updated with a new value.
Dependency tracking also works for computed observables. Computed observables are the observables that are dependent on one or more other observables. The value of the computed observable is updated every time the value of one of its dependencies changes.
Let's extend our contact model to add first and last name:
var contact = { id: ko.observable(1), firstName: ko.observable('John'), lastName: ko.observable('Jones'), phoneNumber: ko.observable('00001111') };
Now that we have added observables for first and last name, let's add a computed observable for full name:
var contact = { id: ko.observable(1), firstName: ko.observable('John'), lastName: ko.observable('Jones'), fullName: ko.computed(function() { return this.firstName() + " " + this.lastName(); }, this), phoneNumber: ko.observable('00001111') };
The fullName
attribute will return the concatenated first name and last name. Knockout will compute the value of fullName
every time the values of either first or last name change.
Dependency tracking allows us to build complex yet sophisticated models that have a set of key attributes and the effects of changing the attributes rippled across the view. Dependency tracking in Knockout is also dynamic. This means that we can have the full name initially dependent on first and last name and then at runtime, add another dependency, say, middle name.
Templating is another very useful feature of Knockout. Templates are the UI structure that renders a UI, based on the provided elements in the template. Templates are useful when you have a requirement of using the same UI structure multiple times in your application. You should not be expected to cut and paste the same structure every time you plan to use it.
The most basic example of a template is when it is used to repeatedly render a row in a table:
<table> <thead> <tr> <th>Contact</th> <th>Phone Number</th> </tr> </thead> <tbody data-bind="foreach: contacts"> <tr> <td data-bind="text: name"></td> <td data-bind="text: phoneNumber"></td> </tr> </tbody> </table>
In the preceding example, we are using the foreach
binding to repeatedly render a table row. The HTML markup within the tbody
element is used as the template to render each contact. Using templates in this way is only useful with control structures, such as loops and conditions. It is not very useful if you plan to use the template in multiple different locations in your application. This is where named templates are handy.
Note
The foreach
binding is the Knockout construct for looping over an array. We will explore foreach
binding in more details later on.
Let's rewrite our previous example to use a named template:
<table> <thead> <tr> <th>Contact</th> <th>Phone Number</th> </tr> </thead> <tbody data-bind="template: {name: 'contact-template', foreach: contacts}"> </tbody> </table> <script type="text/html" id="contact-template"> <tr> <td data-bind="text: name"></td> <td data-bind="text: phoneNumber"></td> </tr> </script>
In this example, we extracted the template into a script block and gave it an ID, contact-template
. We then modified the data binding to add a binding for the template. The template binding takes a name of the template, which is the ID of the script block containing our template. The foreach
binding is a parameter for the template binding.
Templates in Knockout are both flexible and powerful. You can dynamically choose a template by pointing the name
attribute of the template to an observable in your model. You can also add a post processing logic to the template by adding the afterRender
attribute. This attribute can point to a function in your view model that takes the HTML element as a parameter.
Knockout also supports third party templating engines such as jQuery.tmpl
and Underscore. The examples in this book use native Knockout templates. Native templates are more than adequate for most use case.