Model-View-ViewModel (MVVM) is an architectural design pattern that has been popularized by Windows Presentation Foundation (WPF) and Silverlight. For more information on Knockout, see "Ease HTML5 Web Development with jQuery, Knockout, and Modernizr Libraries" and "Improve Your JavaScript Coding with Data-Oriented Programming." Simply put, the pieces of the pattern are these:

  • Model—the model data for the view to consume
  • View—the user interface (UI) that the user actually sees
  • ViewModel—a wrapper class around the model that provides additional functionality, including business logic

MVVM has also gained popularity in various JavaScript frameworks. MVVM's ability to link HTML elements to a data model makes it an attractive offering for managing complex UIs. Most applications have code that performs tasks, such as "if drop-down value is in X state, show another drop-down" or "if user enters too high or low of a numerical value, show a message asking the user to adjust the value." The intent of Knockout and other MVVM framework offerings is to move those conditional logic statements into the ViewModel and let the ViewModel be the sole source of the business logic. In this article, we'll explore some examples that will give you a feel for using Knockout in coding UIs for your web applications.

Getting Started with Knockout

Let's start by first examining a simple example of MVVM without the Knockout JavaScript framework. The sample page in Figure 1 represents a simple check-out screen.

Figure 1: Sample page without Knockout
<div id="AvailabilityPanel">
        <div>Enter your zip for availability of this product:</div>
        <div>
                <input id="zipcode" />
                <input type="button" value="Check" onclick="checkAvailability();" />
        </div>
</div>
<div id="SignupPanel" style="display:none;">
        <div>Product is available, enter quantity (max 5):</div>
        <div>
                <input id="quantity" />
                <input type="button" value="Add to Cart" onclick="cart();" />
        </div>
</div>
<div id="RejectionPanel" style="display:none;color:red;">
        The product is not available or you have too many products in your cart.
</div>
<div id="ConfirmationPanel" style="display:none;color:red;">
        This item has been added to the cart with a quantity of <span id="quantityconf">
</div>

<script type="text/javascript">
        var zipsForAvailability = [ "33426", "00501", "90210", "15239", "19231", "42703" ];

        function cart() {
                var qnt = $("#quantity").val();
               
                if ($.isNumeric(qnt) && qnt <= 5) { //show/hide confirmation/rejection if valid/invalid
                        $("#ConfirmationPanel").show();
                        $("#quantityconf").html(qnt);
                        $("#RejectionPanel").hide();
                }
                else {
                        $("#RejectionPanel").show();
                        $("#ConfirmationPanel").hide();
                }
        }

        function checkAvailability() { //show/hide quantity based on zip
                var zip = $("#zipcode").val();
               
                if ($.isNumeric(zip) && zipsForAvailability.indexOf(zip) > -1) {
                        $("#SignupPanel").show();
                        $("#RejectionPanel").hide();
                }
                else {
                        $("#SignupPanel").hide();
                        $("#RejectionPanel").show();
                }
        }
</script>

The code in Figure 1 is not overly complex, but you can see that we're getting into a lot of state management programming here. As the flow of the form changes state (e.g., valid to invalid zip code, valid zip code to valid quantity to confirmation), the amount of code increases to manage these interactions. For instance, if the user enters an invalid zip code, a rejection message is displayed to the user. If the entered zip code is valid, the user can proceed to enter the quantity and receive a confirmation.

The Knockout MVVM implementation eliminates the need to write these sorts of explicit "if" statements and shifts their functionality into a common ViewModel. The UI reacts to changes in the state of the ViewModel (and vice versa) instead. Figure 2 contains a rewritten version of Figure 1's JavaScript, using the ViewModel to contain all the current states of the form. To do this, we construct a ViewModel using the class shown in Figure 2.

Figure 2: Knockout ViewModel
function viewModel() {
        var self = this;

        this.quantity = ko.observable(0);
        this.zipCode = ko.observable("");
        this.shouldShowRejection = ko.computed(function() {
                return  (self.quantity() < 0 || self.quantity() >= 5) ||
                        self.zipCode().length > 0 && zipsForAvailability.indexOf(self.zipCode()) == -1;
        });
        this.shouldShowConfirmation = ko.computed(function() {
                return self.quantity() >= 1 && self.quantity() <= 5 &&
                        zipsForAvailability.indexOf(self.zipCode()) > -1;
        });
        this.shouldShowSignup = ko.computed(function() {
                return zipsForAvailability.indexOf(self.zipCode()) > -1;
        });
};

Notice how the model uses the "ko" (Knockout) object; this object creates bindings that manage the two-way relationship between the view and ViewModel. The bi-directional relationship happens through the use of observables, objects that track the state of the view and ViewModel. As data changes in either the view or ViewModel, the other is instantly notified of the change.

In addition to observables, computed values create a derived value from observables or other values. Computed values work well for combining information and returning a different result. They also participate in the two-way binding, triggering an update when the inner observable that's used updates its value.

With observables, Knockout is creating a function; to read or write from the quantity value, for instance, you invoke the quantity() function, as shown in the shouldShowConfirmation computed value. The quantity function can also take a parameter value, which then overwrites the value.

At the very end of each model, the model is applied to the view by calling the ko.applyBindings method. This method takes a reference to the view and optionally, a second parameter reference to a parent element to apply the binding to.

Binding to the UI

The Knockout framework applies bindings to the UI by using an HTML5 data attribute. This data-bind attribute identifies two key pieces of information. The first declaration is the binding to apply to the element. A binding essentially specifies an action to take on the element. Bindings may assign a value directly, add or remove Cascading Style Sheets (CSS) classes or styles, show or hide elements, manipulate the Document Object Model (DOM), or perform other types of actions. For instance, the CSS binding adds or removes a CSS class depending on a condition, which is supplied by the second parameter.

This second parameter defines what to bind. It binds observables, computations, or functions defined on the ViewModel. The statement can be the name of the observable member or a conditional statement (which is supported for some bindings). Figure 3 shows several examples of different types of bindings (excluding the entire markup).

Figure 3: Linking the view to the ViewModel
<input id="zipcode" data-bind="value:zipCode" />
<span id="quantityconf" data-bind="text:quantity" />
<div id="SignupPanel" data-bind="visible:shouldShowSignup">
<div id="RejectionPanel" style="color:red;" data-bind="visible:shouldShowRejection">

In Figure 3, the view defines a data-bind attribute specifying how Knockout should apply itself to the element. For the first two text inputs, Knockout will apply the current value of the zipCode and quantity observables defined on the ViewModel. As the text inputs change their value and fire the LostFocus client event, the observable instantly receives the updated value, and vice versa. Notice that within the view, quantity is not a method call as we saw previously in Figure 2, but a reference to the member. Computed values are also referenced in the same way.

You can make expressions more "expressive." For instance, some bindings support the traditional JavaScript evaluation statements. In such cases, we could, for example, define the data-bind declaration as "visible: quantity() > 10". Knockout can, in some cases, use a statement like this for evaluation of the capability. Otherwise, a computed value will always suffice.

Form-Field Bindings

Knockout provides a variety of useful bindings for form elements. In addition to the value binding just described, Knockout provides bindings for enabling and disabling elements. For example, to enable an element based on a ViewModel observable, the HTML element simply needs to apply the "enabled" or "disabled" keyword within the data-bind statement, and Knockout will enable or disable the content if the evaluated ViewModel property returns true. Otherwise, the reverse happens.

Other similar bindings work this way; for example, the hasfocus binding can manipulate which control has the focus of the user's input. The hasfocus binding is great for changing focus when the values provided are empty or invalid and has a variety of other uses.

Knockout also contains a few specialized bindings specific to certain control types. The first binding of this type is the checked binding, which works with controls supporting checked arguments (such as the CheckBox or RadioButton). This binding matches the value of the checked control and tries to match it with the value it is bound to in the ViewModel. The checked binding also works with arrays and will match multiple check boxes to a value in the array.

The last specialized binding I will discuss is for select elements. A select element displays a list of items using a collection of option elements as its children. Knockout can preload and preselect the options list by using the options and selectedOptions bindings. These bindings use an array to populate the list. If the source data happens to be an object, which is a more complex binding, the optionsText and value bindings can be used to specify the value within the bound object to bind to.

Control-Flow Bindings

Knockout supports iterators and conditional statements using the same data-binding syntax as discussed in the previous section; Figure 4 shows an example usage. In Figure 4, the foreach binding iterates through each item in the array and supplies the item at the specified index to the context of the binding. In Figure 4, the parent div specifies the collection to iterate through. The children of the div (the table) represent the template for each item within the array; this means that this template is reused for every item in the list and that bindings should be specified relative to the item level.

Figure 4: Control-flow bindings
<div data-bind="foreach:results">
        <table data-bind="if:isAvailable">
                <thead></thead>
                <tbody data-bind="with:product">
                        <tr>
                                <td><span data-bind="text:name" /></td>
                                <td><span data-bind="text:price" /></td>
                        </tr>
                        <tr>
                                <td colspan="2"><span data-bind="text:description" /></td>
                        </tr>
                </tbody>
        </table>
</div>
<script type="text/javascript">
var viewModel = {
results: [
    {
          isAvailable: true,
          product: { name: "GTX 1000 Laser Pointer", price: "$39.99", description: " .." }
            },
              ]
};

Notice the use of "with" in Figure 4. Similar to Visual Basic's implementation, the "with" statement saves us from referring to the same content over and over again. In referring to the product's information to bind to the UI, we could have referred to each property as "product.name" and "product.description". The "with" statement changes the current context of the binding, thus avoiding the need to type the word "product" multiple times.

Additionally, the "if" binding controls whether or not to show the table based upon the value specified by the "isAvailable" property. If a product isn't available, a value of false prevents the table from appearing. Similarly to the visible binding, "if" and "ifnot" control whether or not something should appear, by actually adding or removing the content from the DOM.

Event Bindings

Knockout also supports setting up event bindings on elements. However, Knockout remains consistent in its event binding definitions, still utilizing the data-bind syntax as I've described. You can bind an element by using this statement: data-bind="click: handle". The standard approach to listening to events can certainly be used, but Knockout provides an added edge: Events fire callbacks defined in the ViewModel, which in turn may update an observable property, which then may update the UI as necessary.

Event bindings support the click event and submit the event information by name directly. To attach to any other event, use the event binding, supplying a hash containing the names of the DOM event and their designated handler. This can be used to attach to events such as mouseover or mouseout. Figure 5 shows an example of attaching to events.

Figure 5: Attaching to events
<div>
        Enter Text:
        <input id="Text" type="text" value=""
                    data-bind="event: { mouseover:over, mouseout: out, keydown:keydown, keyup:keyup } " />
</div>
<div>
        <button data-bind="{ click:clicked }">Submit</button>
</div>
<script type="text/javascript">
var viewModel = {
        keydown: function(data, event) {
                $("<div/>").html("Keydown occurred.").appendTo("#Output");
        },
        keyup: function(data, event) {
                $("<div/>").html("Keyup occurred.").appendTo("#Output");
        },
        over: function(data, event) {
                $("<div/>").html("Mouseover occurred.").appendTo("#Output");
        },
        out: function(data, event) {
                $("<div/>").html("Mouseout occurred.").appendTo("#Output");
        },
        clicked: function(data, event) {
                $("<div/>").html("Button was clicked.").appendTo("#Output");
        }
};
</script>

Notice in Figure 5 how each event has two parameters. The second parameter contains the event information raised with the underlying event that's inherently available through JavaScript. The first parameter contains a reference to the current bound context within the data element. In this simple example, the data element refers to the current ViewModel. In the instances where a foreach looping container is used, the context will be the current item in the loop. The reference is essentially the equivalent of the $data reference. What is $data? Read on.

Context and Scope References

When a ViewModel or one of its members is bound to an element, the context of that element is mapped to the source. This can be important with foreach bindings, where the parent container is bound to the collection but the inner template is bound to the context of each item. Other bindings such as "with" can change the bound context to point to a nested or parent object, thus changing the binding's scope. If, for some reason, you need to access an observable on the ViewModel or from a parent context, there are a few references you can use.

Within a child element, to access the direct parent context, use the $parent reference. In most cases, with simple bindings this will be a reference to the ViewModel. In other cases, such as with control-flow bindings, the $parent reference amounts to the parent object. To check whether an object exists in multiple parents, use the $parents reference, which is an array of the parent objects in the stack, or the $root object, which is a pointer to the ViewModel.

Sometimes you might need to access the object within the current binding. The $data binding refers to this object and can be useful as a shortcut to the object, especially if the "this" pointer is pointing to something other than the current object/element. To illustrate this point, Figure 6 provides an example of using control containers that reference the parent ViewModel. The code in Figure 6 isn't practical; it is meant to help you understand scope.

Figure 6: Scope example
<div data-bind="foreach:results">
        <table>
                <thead></thead>
                <tbody data-bind="with:product">
                        <tr>
                                <td><span data-bind="text:$data.price" /></td>
                                <td><span data-bind="text:$parent.isAvailable" /></td>
                               
<td><span data-bind="text:$parents[0].canPurchase($data)" /></td>
                                <td><span data-bind="text:$root.canPurchaseItem($parent)"/></td>
                        </tr>
                </tbody>
        </table>
</div>

<script type="text/javascript">
var viewModel = {
        results: [
                {
                        isAvailable: true,
                        product: { name: "GTX 1000 Laser Pointer", price: "$39.99", description: ".." },
canPurchase: function(item) {
                                return "Yes";
                        }
                }
        ],
        canPurchaseItem: function(item) {
                return item.isAvailable ? "Yes" : "No";
        }
};
ko.applyBindings(viewModel);
</script>

In the example in Figure 6, the $data reference referred to the current product object. This is because the "with" statement changed the scope of the container to the product. While $data refers to the width, $parent refers to the individual result, scoped at the foreach level. This allows us to refer to the isAvailable property directly. The $parents method provides the same result, returning the value of the isAvailable property. However, in this context, $parents also keeps drilling up the scope chain until it reaches the ViewModel. Here, it finds the reference to canPurchase and invokes it. Notice that this method defines one parameter; the parameter passed in is the object bound at the current scope (namely, the result).

Templates

In our foreach example binding in Figure 6, the container element defining this binding contains child elements that represent the template for each item that's actually bound. Knockout extracts this template and uses it for each item bound to the list. An alternative to this templating scheme—which is the native templating approach that Knockout uses—is to leverage an external framework for templating, such as the jquery-tmpl plug-in or the Underscore or Prototype templating engines. A discussion of using an external framework is outside the scope of this article. Yet another templating approach and the one I'll focus on here is named templates. A named template looks like the code in Figure 7 and can be applied to an HTML element using the syntax shown in the figure.

Figure 7: Using named templates
<script id="peopleScript" type="text/html">
        <ul data-bind="foreach: $data">
                <li>
                        Name: <span data-bind="text: name">
                        State: <span data-bind="{ text: state }">
                       
                        <span data-bind="visible: ($root.inState($data))">
                                (In State)
                        <span>
                </li>
        </ul>
</script>

<div data-bind="template: { name: 'peopleScript', data: itemData }"> </div>

A named template is defined within a script tag, using the type of "text/html". An ID is required so that the template can be applied to a data binding. The template itself is used for each item bound to the template. For instance, if the template is used to represent the template of each item in a foreach statement, the bindings should reflect an item in the list. So, for example, if the template is used for binding a list of people, the template's bindings should reflect the person object bound to the list.

Within the template, there are a few shortcuts you can use if you need to. The shortcuts $data, $parent, and $parents can be used within templates to refer to objects at specific scope levels, depending on how the binding is established.

Observable Arrays

So far we've looked primarily at simple implementations of observables. However, observables aren't just for single-level objects, they are also for arrays. Array-level observables do not observe their object's values but instead observe the state of a collection. As items are added to the array, new entries are added to the UI.

For instance, take a look at Figure 8. In this example, the initial array, stored in the data property, contains four items. As the user clicks the add button, additional items are pushed into the array, which also updates the UI with a new blank item. As the user fills in these values, the objects within the array are fed these values from the view. This functionality displays the role of the observable array, which is to track the array state but leave it up to the individual object in its collection whether to track individual values.

Figure 8: Using observable arrays
<div id="Array">
        <div data-bind="foreach:data">
                <div>Name:<input data-bind="value:name" /> </div>
                <div>State:<input data-bind="value:state" /></div>
        </div>
       
        <div>
                <input type="button" value="Add New" data-bind="click:addItem" />
        </div>
</div>
<script type="text/javascript">
        function arrayViewModel() {
                var self = this;

                self.data = ko.observableArray([
                        { name: ko.observable("Ted"), state: ko.observable("FL") },
                        { name: ko.observable("Bob"), state: ko.observable("AZ") },
                        { name: ko.observable("Andy"), state: ko.observable("TX") },
                        { name: ko.observable("Rajeev"), state: ko.observable("NV") }
                ]);
                self.addItem = function() {
                        var item = { name: ko.observable(""), state: ko.observable("") };
                        self.data.push(item);
                };
        }

        var _arrayModel = new arrayViewModel();
        ko.applyBindings(_arrayModel, $("#Array").get(0));
</script>

A Powerful UI Framework

Knockout is a powerful framework for managing the UI. It uses an object called an observable to link the view to the ViewModel, establishing a two-way binding. Various types of bindings can be applied to the UI for manipulating the user appearance, form values, and much more. Additionally, control-flow bindings provide iterative capabilities within the UI, using both native and named templates, to iterate through data like arrays. Arrays can also be observables and have their state tracked as well. Now that you've learned the essentials of Knockout, please join us for the final article in this HTML5 and jQuery series which focuses on structuring jQuery Ajax calls in web applications.