Knockout: data binding in Javascript

 

Knockout: introduction

Knockout is a Model-View-ViewModel framework for Javascript. In this post I will explain the MVVM pattern, the concept of data-binding, and will give some examples of usage. At the end of the article you will find a sample file with all the code.

Data-oriented vs Control-oriented programming

Whenever I create a new web project, the same question arises. Do I split the files to keep the HTML separated from the Javascript?

As Dan Wahlin explains here, if we separate the code then, using the traditional control-oriented approach the code will look similar to this:

$("#txtName").val("…");

$("#txtAge").val(28);

The files are coupled. The Javascript file needs to reference the HTML controls by using their ID. It is not a bad solution, it works.

But if I add, remove or rename any control, then I have to remember to change the JS file as well.

An alternative to this is to use a data-oriented approach. The main difference is that the former needs to know how to be linked to the data, while the latter just needs to know what data to be linked to.

This can be achieved using a data-binding framework, such as Knockout. It means that you can link a control to an object, to get or set its value. For Javascript you will need a framework, such as Knockout.

MVVM

Model-View-ViewModel is an architectural pattern, used in Knockout, which “… facilitates a clear separation of the development of the graphical user interface (either as markup language or GUI code) from the development of the business logic or back end logic known as the model.” (Source: Wikipedia.org)

The Model represents the domain data. It has no behavior, except maybe for some data validation. All the business logic should be in the View Model. Finally, the View is the layer where the data is displayed, and it only interacts, as you may expect, with the View Model.

In a traditional scenario you will consume the data from a service (which interact with the model), and show it in a web page (the view). You must define the View Model in your Javascript code. See the example in the following section.

Knockout data binding

For getting started, let’s create a simple ViewModel object, with the company information.

function CompanyViewModel() {
        this.name = "UruIT";
        this.country = "Uruguay";
}

And our view will be an HTML two textboxes, and one label to display the name.

<label>Company name:</label><input data-bind="value: name" />

<label>Country:</label><input data-bind="value: country" />

<label data-bind="text: name"></label>

Look at the data-bind attributes in the HTML elements. They specify that the inputs values will be the name and country fields of our view-model object. The last label has its text bound to the name as well.

But in order to get those values, Knockout has to be activated, with an instance of our view-model object:

ko.applyBindings(new CompanyViewModel());

In this example, if you change the input values the underlying ViewModel won’t change. This is because we haven’t configured the dependency tracking yet. So, we have to change the way the ViewModel’s fields are defined, like this:

function CompanyViewModel() {    
     this.name = ko.observable("UruIT");
     this.country = ko.observable("Uruguay");
}

The observable properties are capable of notifying the UI that its value has changed. If you run the example now, and change the company name, you will see that the label is automatically updated.

Computed values

Knockout supports the definition of fields that depend on others. For example if we want to have a field with both, the company name and country, we would add a new property in the view-model as shown here:

this.companyInfo = ko.computed(function() {
        return this.name() + " / " + this.country();
        }, this);

We can bind this field to any control, as we did with the other properties and elements. Note that as the name and country are observables, they must be treated as functions.

Other bindings

We have seen how to bind the value and text properties of some HTML controls. But Knockout is not limited to that.

You could bind the click event of a button to a function defined in the view-model, or bind the href property of a <a>, or the src of an image and more. Here are some of the possible bindings we could make:

Visible

If bound to a false value, it sets the display style to none. Otherwise the display style is removed, making the element visible.

In the following example, the element will be visible only if the result of the function is greater than zero:

<div data-bind="visible: testFunction() > 0 }" />

Css

With this binding, we can add or remove a css class of an element, depending on a condition.

In this example the myCssClass is assigned to the div, only if the result of the testFunction is less than zero.

<div data-bind="css:{ myCssClass: testFunction() < 0 }" />

Attr

This binding allows you to set any element property.

For instance, we can set the href of an anchor tag, or the src of an image:

<a data-bind="attr: { href: url }">Link</a>

<img data-bind="attr: { src:imagePath }"/>

Control flow: foreach

In cases you have to display several items, using the same template, you can use a foreach data binding. This way all the items of an array will be rendered.

In this example, there is an array in our ViewModel object. We want to display them in an unordered list in HTML.

function TechnologiesViewModel() {
       this.languages = [
                 { name:"C#" },
                 { name: "C++" },
                 { name: "Java" }
        ];
}
<ul data-bind="foreach:languages">
       <li data-bind="text:name"></li>
</ul>

The “languages” array, contains three objects which only have one property: name.

A new list item will be created for each element in the languages array. In case you have a string array, you will have to bind each element to the $data variable, which will contain the current string.

Further reading

If you are interested in learning more about Knockout, I suggest you to check the documentation.

 

Posted by: Matias Delgado (matias.delgado@uruit.com), Federico Rodriguez (federico.rodriguez@uruit.com)

UruIT (www.uruit.com)

No Comments