C# Corner

JavaScript Data Binding with Knockout.js

Get powerful, client-side data binding in JavaScript using Knockout.

In this article, I'm going to look at the JavaScript data-binding library Knockout.js. I'll expand on my previous article by removing the external templating library and using Knockout to bind and render the data.

One important thing to point out is that I won't be touching the server-side code at all. One goal of my previous article was to expose my application's functionality as raw data -- JSON data, to be precise. By removing the use of partial views (which are data + presentation combined), my options for how I work with the data have increased enormously. Last time, I used EJS (Embedded JavaScript). Today, I'll use Knockout. In the future, who knows? But the important point is that because of my architecture, I have options.

Knockout vs. jQuery
I hesitated using "vs." in the subhead of this section, because jQuery and Knockout do not compete with each other. jQuery is a marvelously efficient abstraction over the varied and inconsistent DOM APIs exposed by different browsers. And you can accomplish all the functionality of Knockout using jQuery alone; it's just that it would take a lot of jQuery code.

A simple example is the concept of enabling a button once the user has entered some data in a textbox. I might implement that in jQuery like this:

HTML:
<input type="text" id="firstName"/>
<input type="button" id="addButton" value="Add" disabled="disabled"/>
JavaScript:
$('#firstName').on('change keyup input', function() {
    var data = $('#firstName').val();
    if (data.length > 0) {
        $('#addButton').removeAttr('disabled');
    } else {
        $('#addButton').attr('disabled', 'disabled');
    }
});

Nothing too difficult, to be sure. But start adding more and more textboxes, enabling/disabling of various controls on the entire page, collections/tables/lists and things can get complicated pretty quickly.

In contrast, the same functionality as above can be expressed in Knockout as this (NOTE: don't worry if you don't understand the "data-bind" attribute; I'll be covering that later):

HTML:

<input type="text" data-bind="value: firstName, valueUpdate: 'keyup'" />
<input type="button" value="Add" data-bind="enable: firstName().length > 0" />

No special JavaScript is required; Knockout handles it.

Data Binding Basics
Knockout is an MVVM (Model-View-ViewModel) implementation. It separates the view from the data (i.e. the model) via the view model; a "value converter," so to speak. The view model exposes the data from the model along with other functionality to manipulate the model. Knockout takes the view model and binds it to the UI; the browser's HTML. In addition, Knockout hooks up event handlers so that changes in the browser are automatically reflected in the view model. Likewise, changes in the view model cause immediate updates to the HTML; no more writing jQuery event handlers to keep model and data in sync!

Here's a simple JavaScript view model that Knockout can utilize for data binding:

function PersonViewModel() {
    this.firstName = ko.observable('Patrick');
    this.lastName = ko.observable('Steele');
}

As you can see, my properties are not "regular" JavaScript properties (like "this.age = 39"). Instead, Knockout has an object called an "observable". Observable objects support event subscribers to notify them of changes to their data. In the example above, I defined a "firstName" and "lastName" field that are observables, and I initialized the value of each one, too. This enables me to use Knockout to bind these fields to HTML elements and keep the view model updates with changes in the HTML element.

You can bind regular properties with Knockout, but you won't get updates from the HTML element. And changes in the view model won't be reflected in the HTML. If you've got a piece of data that isn't going to be updated at all, there's no need to make it an observable. Just use a regular property.

Binding to HTML
Now, how would I bind my HTML to the "firstName" and "lastName" properties? Knockout recognizes a custom attribute called "data-bind" for any HTML element. It uses this attribute to know how a view model field will be bound to an HTML element. Using the PersonViewModel sample from above, I can bind the two properties to text boxes:

<input type="text" data-bind="value: firstName" />
<input type="text" data-bind="value: lastName" />

All of this is put in place by passing an instance of your view model to ko.applyBindings (usually called in your document.ready handler):

ko.applyBindings(new PersonViewModel());

The "data-bind" attribute contains a collection of comma-separated options for how Knockout should bind a view model property to an HTML element. The two examples above use only a single option: the "value" binding handler. This binding keeps the "value" attribute of a form field (like an <input>, <textarea> or <select>) in sync with a field in the view model.

Earlier, when I showed the example of using jQuery vs. using Knockout, my data-bind attribute had an additional option:

<input type="text" data-bind="value: firstName, valueUpdate: 'keyup'" />

The "valueUpdate" option tells the value binding handler when it should perform its update of the view model. By default, the update is done on lost focus. However, by using the "valueUpdate:" binding, I can control when the view model is updated. In this case, I told it to update on every keystroke.

Computed Observables
Often times, the value to be displayed is not always directly represented in my model. My PersonViewModel is a good example: my data model contains a first name and last name, so I created two observables on my view model that represent each of those fields. But there may be a place where I want to display the person's full name. I could do this with data binding:

<span data-bind="text: firstName" /> <span data-bind=text: lastName" />

But this is mixing formatting and display. If I decide to format the person's name as "last name, first name", I have to re-arrange the HTML.

The proper way to handle this is to let the view model do the formatting, and leave the display to the HTML. This is where a "computed" observable comes in to play. A "computed" observable is a special type of observable that gets its data from other observables in the view model. Here's how I could define a fullName computed observable:

<pre class="codesnippet">
this.fullName = ko.computed(function() {
    return this.firstName() + ' ' + this.lastName();
}, this);

Note the use of parenthesis to call the firstName and lastName observables. Remember, these are not regular JavaScript properties; they're special functions that handle data binding subscribers. Therefore, to get the current value, I must call the observable function. Likewise, when I want to set the value of an observable, I must call the observable function and pass the value as the first parameter.

A computed observable can be found just like a regular observable:

<span data-bind="text: fullName" />

But the real beauty of a computed observable is that it tracks the observables it's using, so if firstName or lastName change, anything bound to fullName is automatically updated. The dependency tracking in Knockout can eliminate tons of code used to manually update the DOM.

Bindings, Bindings Everywhere
So far I've just shown a couple of bindings, value and text. Knockout has a ton of other bindings, such as:

  • Visible. Makes a DOM element visible or invisible based on the value of the observable it is bound to.
  • Options. Used to populate a <select> list. In addition to the options, the value binding can be used to track the currently selected option. No more accessing "selectedIndex" and then indexing in to some array.
  • Css. Automatically add or remove CSS class assignments.
  • Foreach. Loop through a collection and apply bindings for each item in the collection.
  • Enable. Enable/disable an element based on the value of its observable.

There are many more. Check out the documentation for a complete list of binding handlers.

The Click Binding Handler
Knockout also supports declarative event binding. There's a special binding handler for the click event ("click") as well as a generic handler for any DOM event ("event"). Both of these allow me to automatically bind any event to a function in my view model.

Let's say I wanted to flip my person's firstName and lastName. I define the functionality inside my view model:

this.flipNames = function() {
        var first = this.firstName();
        this.firstName(this.lastName());
        this.lastName(first);
    };

Again, note the use of parenthesis. I call my firstName observable without any arguments to get the current value. I then call the firstName and lastName observables to set their value. This will trigger any subscribers (like HTML elements or computed observables) to be updated as well.

Now I can bind this functionality directly to an HTML button:

<input type="button" value="Flip" data-bind="click: flipNames" />

Let's look at my complete PersonViewModel as it currently stands:

function PersonViewModel() {
    this.firstName = ko.observable('Patrick');
    this.lastName = ko.obserable('Steele');

    this.fullName = ko.computed(function() {
        return this.firstName() + ' ' + this.lastName();
    }, this);

    this.flipNames = function() {
        var first = this.firstName();
        this.firstName(this.lastName());
        this.lastName(first);
    };
}

Notice there are no jQuery selectors that target a particular HTML element. No hooking up of event handlers via "on". Nothing that says this JavaScript object is tied to HTML elements. That's loose coupling and gives me a more testable and more maintainable design.

Refactoring the Last Article
Now I'll show how I refactored my last article's sample code to use Knockout for data binding and event handling. To start, here's a quick recap of the data exposed by my sample application:

  • A collection of "Category" objects (called "Categories"). Each Category object consists of a "Name" and a collection of "Subscription" objects (called "Subscriptions").
  • Each Subscription object contains a "Name", a "Url" and an "UnreadCount" which represents the number of items at the Url which the user has not read.
  • An array of strings which represent the names of each Category. This was used to generate a <select> list when adding new items.

I'll start down at the "Subscription" object level and create a JavaScript model which can contain the subscription information (I'll cover changes to the HTML structure later):

function subscription(url, name, unreadCount) {
    var self = this;
    
    self.url = url;
    self.name = name;
    self.unreadCount = ko.observable(unreadCount);
}

Notice that URL and name are not Knockout observables. Not everything in my view model has to be an observable. In this sample, the URL and name of the subscription will never change; there's no need to create an observable with full functionality of dependency tracking and subscriptions if it's never going to change. I can still use it for data binding, I just don't need all the functionality provided by an observable.

Two other things I'll want to add to my subscription model. When displaying a subscription, I had some formatting that looked like this (this is the Razor code; the EJS code was similar):

<div class="offset1 span6">[@sub.UnreadCount] @sub.Name</div>

I displayed the unread count in brackets in front of the subscription name. Instead of formatting this in HTML, I'll do it inside my view model and bind to the formatted value:

self.displayName = ko.computed(function () {
    return '[' + self.unreadCount() + '] ' + self.name;
});

Second, the selection of what CSS class to use (based on the number of unread items) was also buried inside the Razor code:

<div class="row @(sub.UnreadCount > 0 ? "unread-items" : "no-items")">

Instead, I'll create a function to determine the name of the CSS class to apply, and then use Knockout's "css" binding handler to automatically add the class to the element:

self.subStatus = ko.computed(function () {
    return self.unreadCount() == 0 ? 'no-items' : 'unread-items';
});

Next, the category object. This object contains a collection of subscriptions. Collections in Knockout are handled by a special type of observable called an observableArray. An observableArray tracks adds and deletes and automatically notifies subscribers when the size of the array changes. The observableArray does not track changes to the contents of items within the list (even if they are observable). It only tracks changes to the list itself, adding and deleting.

function category(name, items) {
    var self = this;
    
    self.name = name;
    self.subscriptions = ko.observableArray([]);
    $.each(items, function(index, item) {
        self.subscriptions.push(new subscription(item.Url, item.Name, item.UnreadCount));
    });
}

I initialize the subscriptions observableArray to an empty array. Then I loop through each subscription and create a new subscription JavaScript model (which contains the observables and functions for handling an individual subscription).

Now I can create the main view model that the page will be bound to. As noted above (and shown in the previous article), the view model we get from the server will contain a collection of Category objects as well as Category names. Like I did with subscriptions, I'll need to create an observableArray of Category objects. I'm going to encapsulate that inside of a function since I'll need it a couple of times: once when I initially load the list and again when I refresh the list after an add:

function mainViewModel(serverModel) {
    var self = this;

    self.loadCategories = function (cats) {
        self.categories.removeAll();
        $.each(cats, function (index, cat) {
            self.categories.push(new category(cat.Name, cat.Subscriptions));
        });
    };

    self.categories = ko.observableArray([]);
    self.loadCategories(serverModel.Categories);
    self.categoryNames = serverModel.AllCategories;

    self.selectedCategory = ko.observable('');
    self.itemName = ko.observable('');
}

Those last two items -- selectCategory and itemName -- will be used for adding new items to my list. Also, a convention I use whenever I'm integrating a server-side model (from something like MVC) and a client-side view model (in Knockout) is to call the variable "serverModel". This makes it clear (to me) that this is a representation of the model from the server and not a view model I will be binding to.

HTML Changes
Now I can change my views to use Knockout data binding for generating the HTML. The original code to display the list of categories was broken into two parts: a server-side Razor view which was displayed on the initial load of the page, and a client-side template used by EJS to update the HTML as needed. I can eliminate both of those and place the data-bound HTML elements right where I want them. Here's the old Razor code:

@using MovingFromPartialViews.Models
@model IndexModel

@foreach (var cat in Model.Categories)
{
    <div class="row category">
        <div class="span12">@cat.Name</div>
    </div>
    foreach (var sub in cat.Subscriptions)
    {
        <div class="row @(sub.UnreadCount > 0 ? "unread-items" : "no-items")">
            <div class="offset1 span6">[@sub.UnreadCount] @sub.Name</div>
            <div class="span2">
                <button class="btn btn-mini" type="button" action="del"
data-name="@sub.Name">Delete</button>
            </div>
        </div>
    }
}

I replace that with the following HTML (using the data-bind attribute for Knockout binding):

<div data-bind="foreach: categories">
    <div class="row category">
        <div class="span12" data-bind="text: name"></div>
    </div>
    <div data-bind="foreach: subscriptions">
        <div class="row" data-bind="css: subStatus">
            <div class="offset1 span6" data-bind="text: displayName"></div>
            <div class="span2">
                <button class="btn btn-mini" type="button">Delete</button>
            </div>
        </div>
    </div>
</div>

The "foreach" binding handler loops through all items in the bound observable and generates the HTML representation defined inside the element. The first div displays the category name (data-bind="text: name"). Notice that I only used the category name. Since this binding appears inside a foreach loop, the context under which property names are resolved for data binding is each element of the foreach; in this case, a "category" object. Later on, I have another foreach binding for each subscription in the list. Again, property names inside that binding are resolved within the context of an individual "subscription" object, so I only needed to provide the "subStatus" property (for the "css" binding) and the "displayName" property (for the "text" binding). I didn't have to do any kind of indexing into the category and then into the subscription (i.e. "categories[i].subscriptions[j].displayName"). Knockout knows the context under which it's binding elements and is smart about such things.

Next, I need to add some functionality for adding new subscriptions and removing existing subscriptions. I'll take the original "add" code and move it to my mainViewModel:

self.addNewItem = function() {
    $.ajax({
        url: '/home/add',
        type: 'POST',
        data: {
            CategoryName: self.selectedCategory(),
            Name: self.itemName()
        },
        success: function (model) {
            self.loadCategories(model.Categories);
            self.itemName('');
        }
    });
};

I made only a few minor changes to this code:

  • I didn't need to use jQuery to get the current value of the UI element. Since they're bound using an observable, I can just grab the current value from the view model and send it to the server.
  • I use the "loadCategories" method to update the view model with the most current data from the server.
  • When clearing the item name, I just call the observable with an empty string, updating the view model and HTML at the same time.

The HTML code that binds to this method is pretty straightforward:

<div id="addItem">
    <div class="row">
        <div class="span3">
            <select data-bind="options: categoryNames, value: selectedCategory"></select>
        </div>
        <div class="span9">
            <div class="input-append">
                <input type="text" class="span8" data-bind="value: itemName"/>
                <button class="btn btn-primary" data-bind="click: addNewItem">Add</button>
            </div>
        </div>
    </div>
</div>

The "options" binding handler is used to populate the <select> list. I also use the "value" binding so the currently selected category name is maintained in the view model. And notice the use of the "click" handler to hook up the "Add" button to call in to my view model's "addNewItem" method.

Finally, I need to implement deleting a subscription from a category. I'll add this code to the category object since it handles the subscriptions:

function category(name, items) {
    // ...
    // previous code omitted for brevity
    // ...
    
    self.removeItem = function (item) {
        $.ajax({
            url: '/home/delete',
            type: 'POST',
            data: {
                subName: item.name
            },
            success: function () {
                self.subscriptions.remove(item);
            }
        });
    };
}

And I hook this up to the "Delete" button as follows:

<button class="btn btn-mini" type="button" data-bind="click: $parent.removeItem">Delete</button>

Note the "removeItem" function receives the item to delete. This happens automatically with Knockout. When the data-bind attribute is being evaluated, the context of the binding is an individual subscription object. When calling a click handler, Knockout will pass a reference to the binding context as the first parameter. This gives me a direct reference to the item being deleted, not an index into some array or a "key value" I need to look up. I get a reference directly to the subscription item that was bound. I pass its name to the server for deleting; upon success, I remove the item from the observableArray. This will trigger an automatic update of the subscriptions list. I don't have to do anything!

The next thing you probably noticed is the click handler being bound to "$parent.removeItem". Remember the binding context. At the point where that binding is happening, the context is an individual subscription item. The subscription item doesn't have a "removeItem" method. The "removeItem" was defined on the category object, which is the parent to the subscriptions list I'm iterating over. Knockout exposes that via a special "$parent" variable. I can also use "$root" to get access directly to the root-level view model. In this case, I just needed to "move up" one level to the category object, so I bind to "$parent.removeItem".

At this point, the conversion is almost complete. The only thing I need to do is initialize the view model and apply the bindings:

$(function() {
    var serverModel = @Html.Raw(Json.Encode(Model));
    var viewModel = new mainViewModel(serverModel);
    ko.applyBindings(viewModel);
});

All done. By using Knockout, I was able to standardize on one type of templating engine for generating the HTML. I also removed all jQuery selectors on HTML elements (loosely coupled). And I created a set of JavaScript objects which are completely testable by something like Jasmine, since they don't have any reliance on a browser UI.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube