UI Code Expert

Essential Knockout.js, Part 1

Learn how Knockout can help you tame the morass of JavaScript and HTML programming and reduce the time spent writing boilerplate code.

Click here for Part 2

If you're a traditional event-based UI developer, the stateless world of the Web can seem like a daunting and scary place. The shift from traditional server-side processing to client-side Web applications, however, provides a strong step toward an event-based UI world. It combines the massive number-crunching power of server-side data processing with the agility and reactivity of a client-based application. Client-based interactive UI on the Web predominantly means a heavy dose of JavaScript manipulation of HTML and CSS. Although JavaScript is an interesting language with a colorful past (it was originally named Mocha and was created in 10 days), its simplicity gives it great power.

The problem, however, is that the simplicity of JavaScript means it lacks a lot of conveniences and shortcuts. This is especially apparent when you apply values from client-side code to elements in the HTML Document Object Model (DOM). For the values to transfer back and forth between DOM elements and JavaScript code, there must be a lot of calls to document.getElementById (which has variable behavior per browser) and event bindings directly in the HTML (onBlur or onClick attributes).

To make matters worse, JavaScript code that's developed in this manner is hard to debug, hard to maintain and contains massive amounts of duplication. In this article, we'll look at a framework that tames the morass of JavaScript and HTML programming and reduces the time spent writing boilerplate code, all while making designs easier to test, browser-independent and free of repetition. This framework is called Knockout.js, or simply Knockout. Over the next several columns, we'll go through Knockout in detail.

Observations on Observing Observables
At the core of Knockout is the concept of Observables. This concept is not something new with Knockout; it's very much like the System.ComponentModel.INotifyPropertyChanged interface starting back in the Microsoft .NET Framework 2.0 days. It's a product of the Observer pattern put forth in the famous "Design Patterns: Elements of Reusable Object-Oriented Software" (Addison-Wesley Professional, 1994) book by the "Gang of Four": Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides.

The Observer concept describes an object maintaining a list of dependents and notifying those dependents when the object's internal state changes. Perhaps it's best summed up in an example: Imagine you're a powerful media big shot with a dedicated personal assistant. Obviously, you have a complicated schedule with many people vying for your time and attention. You're being whisked down the road in the plush backseat of your Bentley motorcar, when all of the sudden a tire blows out and your driver makes for the side of the road. Your status of "on the way to the country club" just changed to "stuck on the side of the road." Your first call is to your assistant so that he can begin reorganizing your day. In a sense, your assistant (who depends on you for status updates) has subscribed to your state. Likewise, all the people on your schedule that day are dependents of your assistant. In this way, your state-change event ripples outward through the world in a chain of phone calls, e-mails and texts. This is very much like a dependency chain you can make in code using the Observer pattern. Observables are objects that display this behavior.

With Knockout, you can easily make any property of a JavaScript object observable, and even chain observables together to form complex systems of publication and subscription. Knockout allows you to easily inject this behavior into your code without a lot of ceremony or complexity. With the framework added into an HTML page (preferably from a common source, such as the Microsoft AJAX Content Delivery Network, or CDN), in JavaScript all you need to do is assign the value of your properties to be observable:

var bigShotMediaGuy = {
  roadStatus: ko.observable( "flat tire" ),
  cashOnHand: ko.observable( 2000 ),
  businessPapers: ko.observableArray( ["movie script", "take-out menu"] )
};

Additionally, you might want to get status updates from a list of things, such as when things are added or removed. To handle this, Knockout provides an ObservableArray. An important note here: Storing objects in an ObservableArray won't change the stored objects to have observable properties automatically. You can store such objects in an ObservableArray, but you have to explicitly make their properties observable as well.

Once properties become observable, you have to work with them in a different way in JavaScript. This is because not all browser implementations support JavaScript getters and setters. So, for compatibility, Knockout turns your properties into functions when they become observable. This means that to set the value of roadStatus to "on the back of assistant's Vespa," you call the observable as a function like this:

bigShotMediaGuy.roadStatus( "on the back of assistant's Vespa" );

Likewise, to read an observable value back out, you call it like a function with no arguments:

var money = bigShotMediaGuy.cashOnHand();

To make things easier, Knockout observables return their parent so you can chain multiple assignments together, thereby creating a fluid API:

bigShotMediaGuy.cashOnHand( 1750 ).roadStatus( "at the tire store" );

Additionally, Knockout has the concept of computed observables. These are properties that can be data-bound like regular observables, but can contain logic that produces a new value, as shown in Listing 1.

Listing 1. Computed observables in Knockout.
<label for="weight">Weight in kilos:</label><input id="weight" 
  type="text" data-bind="value: weight"/><br />
<label for="height">Height in meters:</label><input id="height" 
  type="text" data-bind="value: height"/>
<p>Body mass index: <span data-bind="text: bmi"></span></p>
<script>
  function ViewModel() {
    var self = this;
    self.weight = ko.observable();
    self.height = ko.observable();
    self.bmi = ko.computed(function() {
      if (self.weight() && self.height()) {
        return (self.weight() / (Math.pow(self.height(), 2)));
      }
      return 0;
    });
  }
 
  ko.applyBindings(new ViewModel());
 
</script>

In Listing 1, the value of the "bmi" observable is read-only. Computed observables can also be written to mutate the values of other observables. For example, when working with a currency textbox in the UI, the appropriate cultural formatting may be displayed to the user, while a fixed decimal value is written back to a database.

The Data Ties That Bind
The concept of UI data binding tends to go hand-in-hand with the Observer pattern. You can think of an observable as being a reflection from a model into a view and vice versa -- a property in JavaScript represents data in the model, while an element in an HTML DOM represents the view. Values of one instantly flow bidirectionally through the observable to the other. Observable can be connected from a DOM element to a JavaScript property to a different DOM element and so on, forming elaborate dependency chains.

However, Knockout isn't limited to only data binding to observable properties. If a data-bound attribute specifies a plain-old-JavaScript property or field, Knockout will still alter the DOM to match the property's value. The difference is that this will only happen once, on page load, and if the value of the property changes later the DOM element won't be updated to match. This behavior can be useful in a scenario such as a "rendered on" time stamp that's generated server-side, but won't be updated on the client for the lifetime of the page.

To achieve this, Knockout provides extensions to HTML markup in the form of a declarative syntax. This means you can specify directly in HTML where different DOM elements will get their content, or what happens when they're interacted with. The bindings that Knockout provides can be separated into three groups: display, events and templates.

Display Bindings
Arguably the simplest binding is the text binding. This binding connects the InnerText value of a given DOM element to the property of a JavaScript observable so that when the property changes, so does the InnerText. It also provides simple HTML encoding to avoid scripting or HTML injection attacks. Consider a simple piece of HTML followed by some JavaScript:

<div data-bind="text: message"></div>
    
<script>
  var model = {
    message: ko.observable( "Hello Media Big Shot!" )
  };
  ko.applyBindings( model );
 
</script>

There are three things going on in this code that are interesting. First, the data-bound element on the <div> lets Knockout know that the inner text should reflect the value of the message property on some object. After that, a new object called model is created with an observable property called message. Finally, Knockout is told to do its thing and parse the HTML for bindings and wire them up to the properties of the passed-in object. The placement of the script block after the HTML is also important. The applyBindings function call needs to happen after the DOM is fully loaded, or "ready," otherwise Knockout won't be able to parse everything correctly. This can be accomplished by using a lower-level DOM library like jQuery and its $ function in a script block in the head element of an HTML page:

$( function () {
  // DOM is ready, call ko.applyBindings( model );
} );

Or you can just call the jQuery $ function in a script block at the bottom of the page, because browsers parse top-to-bottom.

Another display binding is the visible binding. It takes a Boolean value and uses it to toggle the visibility of a DOM element and all its children. Because JavaScript is so very flexible, almost anything can be evaluated as true or false. This behavior extends to observables, which means Knockout evaluates empty properties as false. Consider this simple Web page that shows a message after 500 ms have passed:

<div>This text is here from the beginning...</div>
<div data-bind="text: message, visible: message" style="background-color: red;"></div>    
<script>
  var model = {
    message: ko.observable()
  };
  ko.applyBindings( model );
  var t = setTimeout(function(){model.message("I bet you didn't see this coming!")},500); 
</script>

These are just two examples of bindings that control the content or appearance of the bound DOM element. Knockout has bindings for changing style, CSS classes and even the raw, inner HTML. For the most part, these are one-way bindings simply because there's no way to change the bound values through the Web browser's UI. That is to say, a user can't change an element's CSS class in the browser window (excluding F12 developer tools). However, a subset of the bindings available for display exhibits a two-way behavior.

Knockout also contains bindings that are just for form fields. The most basic of these form field bindings is the value binding. Value is most often used with the <input>, <select> and <textarea> HTML elements. For special cases, such as with checkboxes or radio buttons, the checked binding is used. Because most form field tags have the concept of being enabled, Knockout also has an enabled (as well as its inverse, disabled) binding. These are most commonly used in data-validation code.

Other helpful bindings include the hasfocus binding, a one-way binding that returns true when the cursor is active in a given form field. Options and selectedOptions are useful for dealing with dropdown boxes, and uniqueName assures that elements have a valid name attribute so that libraries such as jQuery Validation perform correctly.

Event Bindings
For an agile and responsive UI, binding values to HTML elements isn't enough. You also need a way to bind behaviors to UI elements. As a result, Knockout also has bindings that respond to UI events. The difference between these bindings and the display bindings is that the event bindings are bound to JavaScript functions rather than observable properties. The simplest of these is the click binding. Whenever a DOM element with this binding is clicked, the function bound to it is called. Consider this simple bit of markup that updates the current time:

<div>At the tone, the curent time is: <span data-bind="text: time"></span></div>
<button data-bind="click: updateTime">Update!</button>
 
<script>
  var model = {
    time: ko.observable(),
    updateTime: function () {
      this.time( new Date() );
    }
  };
  ko.applyBindings( model );
</script>

Another interesting feature is that the time observable isn't perturbed by binding the Date object to the innerText property of the <span>, which expects a string. An observable effectively calls toString on whatever object to which it's bound.

Similar to click, the submit binding calls the bound function when a form is submitted, and unless that function returns true, it will not submit the form to the server. By default, the submit binding assumes you're using a submit button to interact with your code (for example, doing input validation) rather than posting data to the server.

The more generic "event" binding can be used with any DOM event, such as mousedown, scroll or resize. With access to every aspect of the DOM event model, you can create especially reactive UIs, as shown in Listing 2.

Listing 2. Creating a reactive UI.
<p>Mouse over the button below:</p>
<button data-bind="event: { mouseover: go, mouseout: stop }">Gas pedal</button>
<h1 data-bind="style: { color: lightColor }, text: lightValue"></h1>
 
<script>
  var model = {
    lightColor: ko.observable(),
    lightValue: ko.observable(),
    go: function () {
        this.lightColor( "green" );
        this.lightValue( "GO!" );
    },
    stop: function () {
          this.lightColor( "red" );
          this.lightValue( "STOP!" );
    }
  };
  ko.applyBindings( model );
</script>

The event binding accepts an object with multiple events within it. This makes for more readable code that clearly expresses how view logic interacts with display elements.

Template and Control-Flow Bindings
Often there are multiple UI states that need to be captured in a single HTML view. Examples of this would be not showing a UI for record details when no record is selected in a listing. Certainly you could surround the details UI with a container <div> and use the visible binding bound to some selected item observable in your model. But the downside of that would be that the markup would still be in the DOM parsed by the browser; CSS style would be applied to make it not display on the screen. To that end, Knockout also provides an if binding (and its opposite, ifnot). If the bound property that the if binding references doesn't evaluate to true or a true-ish value (such as not null), then the bound element and all its child elements won't be included in the markup rendered in the browser at all. This can provide significant render-speed improvements on complex pages. The if binding, like most of the other control-flow bindings, can also be used outside of an HTML element. Knockout accomplishes this with some clever HTML comments. Consider the simple code example in Listing 3.

Listing 3. Using the if binding outside of an HTML element.
<h1>F12 and notice no DIV</h1>
<label><input type="checkbox" data-bind="checked: message" /> Display message</label>
    
<!-- ko if: message -->
<div>
  <ul>
    <li>Thing 1</li>
    <li>Thing 2</li>
  </ul>
</div>
<!-- /ko -->
 
<script type="text/javascript">
  var model = {
    message: ko.observable()
  };
  ko.applyBindings(model);
</script>

With the code in Listing 3, if the F12 developer tools are opened, the DOM doesn't contain the <div> with the list in it (see Figure 1).

However, checking the checkbox causes everything in the comments to be injected into the DOM and rendered in one go. If the injected elements contained data-binding attributes, they would be evaluated as well.

[Click on image for larger view.] Figure 1. F12 developer tools shows the Div and its list are missing.

Arguably the most useful control-flow binding is the foreach binding. This binding enumerates an array and renders an instance of contained elements for each member of the array. If the array is an ObservableArray, any changes in array order or makeup will be reflected in the DOM. These changes are applied without the entire collection being enumerated, which greatly improves the speed and responsiveness of the page. Like the if binding, the foreach binding can also be added to markup through comments with the ko marker.

Sometimes HTML could do with some DRY-ing up, and Knockout provides the template binding to increase reuse in markup. This binding has two modes: native and string-based. Native templates are what the proceeding control-flow bindings are based on. String-based templates are useful for connecting to third-party template engines such as jQuery.tmpl or Underscore. To create a named native template, simply wrap some bindable markup in <script> tags with a type of text/html and an ID of your choosing. Then markup can be reused in multiple instances, as shown in Listing 4.

Listing 4. Reusing markup.
<div data-bind="template: { name: 'human', data: bigShotMediaGuy }"></div>
<div data-bind="template: { name: 'human', data: lowlyAssistant }"></div>
        
<script type="text/html" id="human">
  <p>Name: <span data-bind="text: name"></span></p>
  <p>Cash: <span data-bind="text: cash"></span></p>
</script>
 
<script>
  var model = {
    bigShotMediaGuy: { name: 'Diamond Joe Quimby', cash: 10000 },
    lowlyAssistant: { name: 'Gil Gunderson', cash: 'two bits' }
  };
  ko.applyBindings(model);
</script>

Using templates in this manner can give your code modularity and organize chunks of markup into control-like structures.

As you've seen, there's an incredible variety to the Knockout data bindings, and great care has been taken to provide utility for almost any coding need. In many cases, additional events on data bindings can be used to provide animation or other behavior to state changes of DOM elements. In the next column, we'll demonstrate that if there's a need for a binding that doesn't come in the Knockout box, custom bindings can be created against a simple, yet powerful, JavaScript API.

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