barbarian meets coding

WebDev, UX & a Pinch of Fantasy

Knockout.js Wiki

This article is part of my personal wiki where I write personal notes while I am learning new technologies. You are welcome to use it for your own learning!

Introduction to Knockout.js

Knockout.js is a JavaScript library that allows you have a rich client-side interactivity by applying the MVVM design pattern in the front-end of your web application. It helps separate behavior and structure, provides support for declarative two-way bindings, observable objects and collections and templating.

The example below illustrates how to use Knockout:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- 1. Define data binding in a declarative fashion -->
<input data-bind="value: firstName"/>
<!-- 2. Define View Model with observable properties -->
<script>
    var viewModel = {
        firstName: ko.observable("Jaime");
    };
</script>
<!-- 3. Initialize binding with the applyBindings method-->
<script>
ko.applyBindings(viewModel);
</script>

You can also find a live example in jsFiddle.

Knockout MVVM

The knockout version of MVVM consist on:

  • Model: JSON data coming from your back end that represents an entity in your application’s domain
  • View: your HTML that represents the user interface of your web application
  • View Model: A particular representation of your model that sits between the view and the model. It contains properties and methods to support the view.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Model
var character = {
    "name": "Conan",
        "hP": {
        "Total": 2000,
            "Current": 500
    },
        "equipment": {
        "Armor": "Leather Armor",
            "Weapon": "Two-handed Sword"
    }
};

// View Model
var CharacterViewModel = function () {
    var self = this;
    self.name = ko.observable(character.name);
    self.currentLife = ko.observable(character.hP.Current);
    self.totalLife = ko.observable(character.hP.Total);
    self.life = ko.computed(function () {
        var totalLifeChars = 10;
        var currentLifeChars = parseInt((self.currentLife() / self.totalLife()) * 10, 0);
        var lifeStatus = "";
        for (var i = 0; i < currentLifeChars; i++) lifeStatus += "*";
        for (var i = 0; i < totalLifeChars - currentLifeChars; i++) lifeStatus += "-";
        return lifeStatus + "(" + self.currentLife() + "/" + self.totalLife() + ")";
    });
    self.equipment = ko.computed(function () {
        return "Wearing a " + character.equipment.Armor + " and wielding a " + character.equipment.Weapon;
    });
};

ko.applyBindings(new CharacterViewModel());

See the full [MMVM with Knockout example in jsFiddle][].

Using Knockout with Visual Studio

You can easily get Knockout within your project by using NuGet:

1
PS> Install-Package knockoutjs

In order to get Knockout JavaScript intellisense either use an extension such as ReSharper or add the line below as the first line of your *.js files:

1
/// <reference path="/scripts/knockout-2.0.0.debug.js" />

Knockout.js Bindings and Observables

Both concepts of bindings and observables are the heart of knockout.js library.

Observables

Knockout uses the concept of observables (in a similar sense to INotifyPropertyChanged) to allow properties in the view model to notify the UI when they are changed, so that the UI can be updated. The use of observables creates a two way binding that will keep the UI and the underlying view model synchronized.

To use observables with knockout you declare properties in the view model as shown below:

1
2
3
var ViewModel = {
    aProperty: ko.observable("helloWorld");
}

Computed observables

Computed observables are a special type of property that is computed/calculated from the values of other observables. When the associated observables change, the computed property is updated.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var employee = function(){
    var that = this;
    that.firstName = ko.observable("John");
    that.secondName = ko.observable("Doe");
    that.fullName: ko.computed(function(){
        return that.firstName() + " " + that.secondName();
    });
}
// or
var employee = {
    firstName : ko.observable("John"),
    secondName : ko.observable("Doe")
}
employee.fullName = ko.computed(function(){
    // this is the "employee" that we pass to the computed method
    return this.firstName() + " " + this.secondName();
}, employee);

You can extend a computer property functionality by using the following expression that allows both reading and writing:

1
2
3
4
5
6
7
8
9
employee.fullName = ko.computed({
    read: function(){
    // return an expression based on observables
    },
    write: function(value){
    // parse information within "value" and save it in observable properties
    },
    owner: employee
})

This construct is quite similar to Value Converters in XAML.

ObservableArray

An ObservableArray is similar to an [ObservableCollection][] in XAML that tracks changes done to the array (objects added or removed). It is important to notice that, in order to track changes within those objects that are part of the array, they need to be observables.

1
2
3
4
5
6
7
8
9
10
11
<script>
var employee = {
    firstName : ko.observable("John"),
    lastName : ko.observarble("Doe"),
    badges: ko.observableArray([
        {name: "adventurer", type: "gold"},
        {name: "independent", type: "silver"}])
}
</script>
// and in the html
<span data-bind="text: badges().length"></span>

ObservableArray provides a lot of functions, see knockout’s Observable Array reference for more information.

Explicitly Subscribing to Changes in Observables

In addition to the standard functionality of observables, knockout provides support for registering to be notified when changes occur. This allows us to hook our own business logic to be executed when observable properties are changed.

1
2
3
4
5
6
var employee = {
    firstName : ko.observable("John")
}
employee.firstName.subscribe(function(){
    alert("firstName was Changed!!!!");
})

If required, you can also unsuscribe from the changes notification by:

1
2
3
4
5
6
7
var employee = {
    firstName : ko.observable("John")
}
var subscription = employee.firstName.subscribe(function(){
    alert("firstName was Changed!!!!");
})
subscription.dispose();

Built-in Bindings

Knockout, as XAML, provides a declarative way to specify bindings. The source code below illustrates how to use some of the built-in bindings:

1
2
3
4
<!-- a textbox -->
<input type="text" data-bind="enable: isEnabled, value: name, valueUpdate: 'afterkeydown'"/>
<!-- a button -->
<button id="the-button" data-bind="click: explode">click to explode</button>

Binding Text and HTML

You can bind text and html using the following bindings:

  • text: Use to bind the text within an element to a view model property
  • html: Use to bind html as a children of an element to a view model property (that exposes html markup)

See example below (full example of binding text and HTML in jsFiddle)

1
2
3
4
5
6
7
8
<div data-bind="html: message"></div>
<div data-bind="text: message"></div>
<script>
var viewModel = {
    message: ko.observable("<p><strong>Hello World!</strong></p>")
};
ko.applyBindings(viewModel);
</script>

Binding Values

You can bind values using the value built-in binding.

1
2
3
4
5
6
7
8
<input type="text" data-bind="value: name, valueUpdate: 'change'"/>
<span data-bind="text: name"/>
<script>
var viewModel = {
    name: ko.observable("Jaime")
};
ko.applyBindings(viewModel);
</script>

Additionally you can use the valueUpdate built-in binding to specify when the value of a specific input element is going to be persisted in the underlying view model:

1
<input type="text" data-bind="value: name, valueUpdate: 'change'"/>

The available options are:

  • change: Default value. Update when moving focus from the input element
  • afterkeydown: Updated immediately when writing
  • keypress: Updated when the key is pressed and held down
  • keyup: Updated when the key is released

Binding Checkbox and Radio Button

In order to bind a view model to a checkbox we use the checked built-in binding whose use is illustrated below:

1
2
3
4
5
6
7
8
9
<div>
    <span>Jaime is Awesome </span><input type="checkbox" data-bind="checked: isAwesome"></input><span data-bind="text: isAwesome"></span>
</div>
<script>
var viewModel = {
    isAwesome: ko.observable(true)
};
ko.applyBindings(viewModel);
</script>

In a similar fashion, to bind a view model to a group of radio buttons we use the checked and value built-in bindings as detailed below (Note how we used the foreach binding to avoid code duplication):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div>
    <h3>Main attribute</h3>
    <div id="attributes" data-bind="foreach: attributes">
        <input type="radio" data-bind="checked: $parent.selectedAttribute, value: key" /><span data-bind="text: name"></span>
    </div>
</div>
<script>
var viewModel = {
    selectedAttribute: ko.observable(),
    attributes: [{key: "STR", name: "Strength"},
                 {key: "INT", name: "Intelligence"}]
};
ko.applyBindings(viewModel);
</script>

Binding Dropdowns and Listboxes

To bind data from a view model to a drop down you use the following built-in bindings:

  • option: To bind to a collection of possible selectable options
  • optionsText: To determine which property within the options will be used to display each option in the dropdown
  • optionsValue: To determine which value will be stored when an option is selected
  • value: To store/read the selected value in the dropdown

See an example for a single selection drop down below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
    <h3>Main attribute</h3>
    <div id="attributes">
        <select data-bind="options: attributes, value: selectedAttribute, optionsText: 'name', optionsValue: 'key'"></select>
    </div>
    <span data-bind="text: selectedAttribute"></span>
</div>
<script>
var viewModel = {
    selectedAttribute: ko.observable(),
    attributes: [{key: "STR", name: "Strength"},
                 {key: "INT", name: "Intelligence"}]
};
ko.applyBindings(viewModel);
</script>

And an example for a multiple selection drop-down (Listbox) below (Note the user of the selectedOptions built-in binding:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div>
    <h3>Main attribute</h3>
    <div id="attributes">
        <select data-bind="options: attributes, selectedOptions: selectedAttributes, optionsText: 'name', optionsValue: 'key'" multiple="multiple"></select>
    </div>
    <span data-bind="text: selectedAttributes"></span>
</div>
<script>
var viewModel = {
    selectedAttribute: ko.observable(),
    selectedAttributes: ko.observableArray([]),
    attributes: [{key: "STR", name: "Strength"},
                 {key: "INT", name: "Intelligence"}]
};
ko.applyBindings(viewModel);
</script>

The Enable and Disable bindings

Knockout also provides the enable and disable built-in bindings that allow us to manage the enable/disable status of an input element by binding elements to our view Model.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div>
    <span>Enable</span>
    <input type="checkbox" data-bind="checked: allowEdit"/>
    <input type="text" data-bind="enable: allowEdit, value: name"/>
    <span>allowEdit </span><span data-bind="text: allowEdit"/>
</div>
<div>
    <span>Disable</span>
    <input type="checkbox" data-bind="checked: isReadOnly"/>
    <input type="text" data-bind="disable: isReadOnly, value: name"/>
    <span>isReadOnly </span><span data-bind="text: isReadOnly"/>
</div>
<script>
var viewModel = {
    name: ko.observable("Jaime"),
    isReadOnly: ko.observable(false),
    allowEdit: ko.observable(false)
};
ko.applyBindings(viewModel);
</script>

Binding the Focus of DOM Elements

You can control the focus on DOM elements by using the hasFocus built-in binding.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<div>
    <h3>hasFocus</h3>
    <input type="text" id="firstName" name="firstName" data-bind="hasFocus: firstNameHasFocus"/><span data-bind="visible: firstNameHasFocus">hello firstName!</span>
     <input type="text" id="secondName" name="secondName" data-bind="hasFocus: secondNameHasFocus"/><span data-bind="visible: secondNameHasFocus">hello secondName!</span>
    <input type="text" id="nationalId" name="nationalId" data-bind="hasFocus: nationalIdHasFocus"/><span data-bind="visible: nationalIdHasFocus">hello nationalId!</span>
</div>
<script>
var viewModel = {
    firstNameHasFocus: ko.observable(true),
    secondNameHasFocus: ko.observable(false),
    nationalIdHasFocus: ko.observable(false)
};
ko.applyBindings(viewModel);
</script>

Binding Click Event and Other Events

In order to bind click events on DOM elements, knockout offers the built-in binding click:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<div>
    <span>Write your Name </span><input type="text" data-bind="value: name"/>
    <button data-bind="click: sayHello">Greetings!</button>
</div>
<script>
var ViewModel = function(){
    var self = this;
    self.name = ko.observable();
    self.sayHello = function(){
        var name = self.name() || "Mr. Unknown";
        alert("Hello " + name + "!");
    };
};
ko.applyBindings(new ViewModel());
</script>

You can also bind your view model methods to other DOM elements events by using the event built-in binding that allows us to specify an arbitrary number of event bindings. See an example below:

1
<div data-bind="event: {mouseover: rainDestruction, mouseout: chill}"></div>

You can find more event bindings in the knockout documentation.

Binding Styles

Knockout provides a series of bindings that allow us to manipulate styles:

  • style: Allows you to set css styles based on a condition
  • css: Allows you to set a css class based on a condition
1
2
3
4
<!-- setting inline sytles based on a condition -->
<input type="text" data-bind="value: temperature, style: {color: isHot() ? 'red':'blue'}"/>
<!-- setting a css class based on a condition -->
<input type="text" data-bind="value: temperature, css: {hot: isHot, cold: isCold}"/>

See full example of binding styles in jsFiddle.

Binding Attributes

You can bind to DOM element attributes by using the attr built-in binding:

1
2
3
<a data-bind="attr: {href: url, title: name}, text: name">
    <img data-bind="attr: {src: photoUrl, alt: altText, title: name}" />
</a>

Useful JavaScript Patterns for Knockout

There are a series of patterns that can save us from some of the bad parts of JavaScript and take advantage of some of benefits of OOP such as encapsulation, separation of concerns, extensibility and maintainability.

Using Namespaces

One of the big fails of JavaScript is that everything has global scope by default, and thus name collisions can become a real danger. Thence, JavaScript developers have found a way to circumvent this language flaw by using objects to define namespaces ($ for jQuery, ko for knockout, etc). For instance, the code example below creates the namespace bmc if it doesn’t exist already:

1
2
3
var bmc = bmc || {};
// once the namespace is defined, we can define anything we want in that namespace and remain safe from name collisions
bmc.sayWhat = function(){alert("Barbarian Meets Coding!");};

Defining View Models Using Object Literals

You can define simple view models as object literals like this:

1
2
3
4
5
6
7
bmc.viewModel = {
  firstName: ko.observable("John"),
  secondName: ko.observable("Doe")
};
bmc.viewModel.fullName = ko.computed(function(){
    return this.firstName() + " " + this.secondName();
}, bmc.viewModel) // see how I need to pass the viewModel explicitly
  • Pros
    • Quick an easy
  • Cons
    • All members have to be public
    • Problems with this (since this in JavaScript represents the owner of a function, depending on who calls a given function this will be the viewModel or not)

Defining View Models Using the Module Pattern

You can also define view models using the module pattern:

1
2
3
4
5
6
7
8
9
bmc.viewModel = (function(){
    var hiddenMessage = "I am private!";
    return {
        publicMessage: "I am public",
        sayHello: function(){
            alert(this.publicMessage + hiddenMessage);
        }
    };
})(); // the function is called, so 'this' is fixed
  • Pros
    • Allows for private and public members
    • Allows to use this more safely
  • Cons
    • You need to access private and public members differently

Defining View Models Using the Revealing Module Pattern

Finally, you can define view models using [the revealing module pattern][] in a very similar fashion to the module pattern:

1
2
3
4
5
6
7
8
9
10
11
12
13
bmc.viewModel = (function(){
    var hiddenMessage = "I am private!",
        sayHello = function(){
            alert(this.hiddenMessage);
    };
    // This would be the 'revealing' part
    // you define the implementations above, and
    // decide what you expose via the object literal below
    return {
        publicMessage: hiddenMessage,
        sayHello: sayHello
    };
})(); // the function is called, so 'this' is fixed

In addition to the pros and cons in the previous section:

  • Pros
    • Makes it clearer what is private and what is public
    • Private members can call each other without needing to use this
    • Clarifies the meaning of this, since you only need to use it in function definitions
    • Allows you to expose private functions with different names

Using Knockout for Templating

Knockout provides support for using templates that allow you to reuse source code. You have the option of either using the native support for templating or use other popular third-parties.

Using Named Templates

You can define named templates by enclosing the template html markup inside a <script> tag with an id as identifier. For instance:

1
2
3
4
5
<script type="text/html" id="playerTemplate">
    <span data-bind="text: gamerTag"></span>
    <span data-bind="text: score"></span>
    <button data-bind="click: kickFromRoom">Kick</button>
</script>

Or by defining it within other html element which is set to display:none as in:

1
2
3
4
5
<div id="playerTemplate" style="display: none">
    <span data-bind="text: gamerTag"></span>
    <span data-bind="text: score"></span>
    <button data-bind="click: kickFromRoom">Kick</button>
</div>

You can then use the template through the built-in binding template as in:

1
<div data-bind="template: {name: 'playerTemplate'}"></div>

Controlling Flow with Knockout

Knockout allows you to control the flow of how the viewModel is

The if and ifnot built-in bindings evaluate a condition and execute the enclosed markup only if the condition is met (or not met in the case of ifnot).

1
2
3
<p data-bind="if: hasValidationError">
    <span data-bind="text: validationError"/>
</p>

The foreach built-in binding allows you to iterate through a array of items, and switch the context of knockout bindings to each item within that array.

1
2
3
4
5
6
7
8
9
10
11
12
<ul data-bind="foreach: cartItems">
    <li data-bind="text: itemName"></li>
</ul>
<!-- or -->
<tbody data-bind="template: {name: 'cartTemplate', foreach: cartItems}"></tbody>
<script type="text/html" id="cartTemplate">
    <tr>
        <td data-bind="text: itemName"/>
        <td data-bind="text: quantity"/>
        <td data-bind="text: price"/>
    </tr>
</script>

The with built-in binding allows you to explicitly change the context of knockout bindings, as in:

1
2
3
4
5
6
7
8
9
10
11
12
<!-- without with -->
<div class="address">
    <p data-bind="text: address().street"></p>
    <p data-bind="text: address().postalCode"></p>
    <p data-bind="text: address().city"></p>
</div>
<!-- using with to change the context of the ko binding -->
<div class="address" data-bind="with: address">
    <p data-bind="text: street"></p>
    <p data-bind="text: postalCode"></p>
    <p data-bind="text: city"></p>
</div>

Inline templates

By using if, ifnot, foreach and with, we are basically creating anonymous inline templates:

1
2
3
4
5
6
7
8
9
10
<ul data-bind="foreach: cartItems">
    <li data-bind="text: itemName"></li><!-- this is an anonymous inline template -->
</ul>
<!-- would generate -->
<ul>
    <li>carrots</li>
    <li>bread</li>
    <li>milk</li>
    <li>cereals</li>
</ul>

Behind the scenes what is really happening is that we are using the built-in templating engine. These built-in bindings are just syntactic sugar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!-- the if and ifnot bindings -->
<div data-bind="if: hasErrors">...</div>
<div data-bind="ifnot: hasErrors">...</div>
<!-- are actually -->
<div data-bind="template: {if: hasErrors}">...</div>
<div data-bind="template: {ifnot: hasErrors]">...</div>

<!-- the foreach binding -->
<ul data-bind="foreach: cartItems">...</ul>
<!-- is actually -->
<ul data-bind="template {foreach: cartItems}">...</ul>

<!-- the with binding -->
<div data-bind="with: description">...</div>
<!-- is actually -->
<div data-bind="template: {if: description, data: description}">...</div>

Binding Contexts

It is important to note, that once you have changed the data binding context with either with or foreach, you can use a series of special expressions to access elements in the viewModel outside of that particular context. For instance:

  • $data: referes to the current context
  • $parent: access parent context
  • $parents: array that represents all parent view models
  • $root: go to the root context

Dynamically Choosing a Template

You can easily change dynamically between multiple templates by binding the template name to an observable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<div data-bind="template: {name: selectedTemplate}">...</div>
<script type="text/html" id="template1">
...
</script>
<script type="text/html" id="template2">
...
</script>
<!-- in javascript viewModel.js-->
var ViewModel = {
    // ...
    self.selectedTemplate = ko.observable("template1");
    // or
    // self.isSelected = ko.observable(true);
    // self.selectedTemplate = function() {return isSelected() ? "template1" : "template2" };
}
ko.applyBindings(new ViewModel);

or through flow control using if or ifnot.

Template Bindings

In addition to the nameand foreach we have just seen, knockout templating engine also offers:

  • data: JavaScript object to provide as context for the template, if none given, uses current foreach context or current context
  • afterRender: callback invoked after DOM elements within the template are rendered
  • afterAdd: callback invoked after DOM elements are added to the template
  • beforeRemove: callback invoked before DOM elements are removed from the template
1
2
3
4
<div data-bind="template: {
        foreach: products,
        beforeRemove: fadeOutProduct,
        afterAdd: fadeInProduct}">...</div>

Containerless Bindings

Knockout provides the option to create inline templates by using HTML comments, as you would with normal inline templates. For instance:

1
2
3
4
<!-- ko with: selectedWeapon -->
  <span data-bind="text: name"></span>
  (<span data-bind="text: modifiers"></span>)
<!-- /ko -->

Containerless bindings have two main benefits:

  • Reduces clutter because you don’t need to add an extra element to host the knockout binding
  • Moves binding logic outside of elements and makes it less obstrusive

Using Knockout with External templates

See Knockout.js External Template Engine.

Custom Bindings

Knockout.js provides a great extensibility point by letting you create your own bindings.

You can create your own bindings by using the following syntax to extend the bindingHandlers object:

1
2
3
4
5
6
7
8
9
ko.bindingHandlers.myCustomBinding = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel){
       // runs the first time the binding is evaluated
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel){
       // runs after init, and every time one of
       // its associated observables changes
    }
}

where the different parameters in the init and update functions are:

  • element: the DOM element bound to the custom binding
  • valueAccessor: the value assigned to the binding
  • allBindingsAccessor: lets you access all other bindings within the same data-bind attribute
  • viewModel: the context view model

Custom bindings are very useful when combining Knockout.js with other libraries like jQuery:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- Html -->
<section class="details" data-bind="fadeVisible: IsShowingDetails(), fadeDuration: 600">
    ...
</section>
<script>
// JavaScript
...
ko.bindingHandlers.fadeVisible = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel){
        var shouldDisplay = valueAccessor();
        $(element).toggle(shouldDisplay);
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel){
        var shouldDisplay = valueAccessor(),
            allBindings = allBindingsAccessor(),
            duration = allBinddings.fadeDuration || 500;
        shouldDisplay ? $(element).fadeIn(duration) : $(element).fadeOut(duration);
    }
}
</script>

or jQueryUI:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- HTML -->
<button class="details" data-bind="jqUIButton: {enable: stateHasChanged()}">Save</button>
<script>
// JavaScript
...
ko.bindingHandlers.jqUIButton = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel){
        $(element).button();
    },
    update: function (element, valueAccessor, allBindingsAccessor, viewModel){
        var value = valueAccessor();
        $(element).button("option", "disabled", value.enable === false);
    }
}
</script>

Unobstrusive JavaScript

Knockout also provides helpers so that you can use JavaScript more unobstrusively – i.e. the concept of separating behavior (JavaScript) from content (HTML)) – and bind your event handlers (for instance for the click event) using jQuery, so that instead of:

1
<button class="callPhone" data-bind="click: $parent.callPhone"></button>

You have:

1
2
3
4
5
6
7
8
<!-- HTML -->
<button class="callPhone"></button>
<!-- JavaScript -->
$("div.phone-list").on("click", "button.callPhone", function(){
    // the ko.dataFor(this) gets the data available for the
    // particular element
    myNamespace.ViewModel.callPhone(ko.dataFor(this));
});

In addition to the dataFor helper, knockout also offers the contextFor method which returns the entire binding context ($data. $parent, $root…) available to any particular DOM element.

If you want to be even more unobstrusive, you can add all knockout bindings via jQuery, as in:

1
2
$("input.firstName").data("bind", "value: firstName");
$("input.lastName").data("bind", "value: lastName");

More information on Knockout’s documentation: Using unobstrusive event handlers.

Knockout and Ajax: Persisting Data

Chances are that sooner or later you will want to pull/save date from/to the server :).

When getting data from a server, we will use ajax (via vanilla JavaScript or jQuery) to call a web service and get a JSON object that can act as Model and data source of our ViewModel.

Likewise, when pushing data to the server, we will generate a JSON object from our viewModel and use ajax to send this JSON object to a web service.

It is a good practice to group this ajax calls as a client-side service layer for retrieving/saving JSON from/to the server.

Change Tracking

Check out Knockout Lite Tools on GitHub for an example of a change tracking implementation by John Papa.

Mapping of Objects

Another interesting tool is the Knockout Mapping Plugin that allows you to directly map JSON into an object with observables and vice versa. Super useful in tandem with Ajax.

Knockout.js Best Practices and Tips

  • Don’t write DOM manipulation code in your ViewModel (remember the MVVM view model should not be coupled with the view, view –> view model –> model). Instead, use custom bindings to wrap DOM manipulation.

Knockout And Durandal.js

Durandal.js is a JavaScript Single Application Framework that:

  • helps us compose views and view models
  • loads modules as they are needed
  • lets you manage the application lifecycle
  • uses async programming with promises
  • it is convention based
  • it leverages a lot of popular open source libraries:
    • knockout.js for data binding
    • require.js for AMD
    • jQuery to manipulate the DOM

Getting Started with Durandal

Installing Durandal

If you are using Visual Studio, installing Durandal is as easy as:

1
Install-Package Durandal

This will install Durandal and all its dependencies. Additionally, it will create an App folder in which all our views and scripts for our Durandal application will be placed (Durandal uses convention over configuration to find modules, templates, etc). The durandal source code itself will be located in App/durandal. We will structure our application in modules and Durandal will use AMD (require.js) to resolve them at runtime.

Adding the Main View/Entry Point of the App

You can start by adding a container div where Durandal will inject its views and a script element to the bottom of your page (in case you are using ASP.NET MVC you can add it within your index.cshtml view).

1
2
3
4
5
6
7
8
9
<body>

<div id="applicationHost">
    <!-- here we will inject the SPA views -->
</div>

<!-- this loads require.js and tells it where it can find the the entry module -->
<script src="/App/durandal/amd/require.js" data-main="App/main"></script>
</body>

This will establish the entry point for your SPA. The main.js file will contain the main module of your app with configuration for require.js and the app itself. It will also specify the root or first view of your app, in this case 'shell'.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// require.js configuration
require.config({
    paths: {'text': 'durandal/amd/text'}
});

// durandal configuration
define(function(require){
    var system = require('durandal/system'),
        app =  require('durandal/app');

    // enable Durandal debug mode
    system.debug(true);
    // start the application and 
    // set the entry view/viewmodel
    app.start()
       .then(function(){
        app.setRoot('viewmodels/shell');
    });
});

You can then continue by creating a main view or shell for your application by adding a shell.html file to your App folder.

1
2
3
4
<section>
    <h1> This is my main  app page!!</h1>
    ...
</section>

And the associated view model within shell.js. Durandal will take care of composing this view-view model pair since they are specified as the root of the application. As soon as the view/view model are comopsed, Durandal will cal the view model’s activate method:

1
2
3
4
5
6
7
8
9
10
define(['durandal/system'])
function(system){
    function activate(){
        Console.log('Durandal SPA app started!')
    };

    return {
        activate: activate
    };
}

In order to explicitely tell Durandal to compose more view-viewmodel pairs, we use the compose knockout binding as illustrated below:

1
2
3
4
5
<section>
    <h1> This is my main page!!</h1>
    <!-- ko compose: {model: 'views/todolist', activate: true} -->
    <!-- /ko -->
</section>

This will tell Durandal to go and fetch the todolist.html/todolist.js pair and render it for us. We can then continue composing views and view models as needed.

Navigation in Durandal

Durandal also provides support for navigation and routing between views via the Durandal.Router plugin. To install it via NuGet just type:

1
PM> Install-Package Durandal.Router

In order to set up navigation in your SPA, you start by configuring the different routes for your application (normally in your shell.js):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var router = require('durandal/plugins/router');

// you can use router.mapRoute to configure each route as shown below

router.mapRoute(
   /* url */ 'todolist',
   /* module */ 'viewmodels/todolist',
   /* name */ 'To Do List',
   /* visible */ true);

router.mapRoute('history', 'viewmodels/history', 'History', true)

//  you can use router.mapNav if view is always visible just like mapRoute.
// or you can use router.map(routes):

var routes = [
    {
    url: 'todolist',
    moduleId: 'viewmodels/todolist',
    name: 'To Do List',
    visible: true
    },
    /* etc */];
router.map(routes);

Once you have configured the routing of your application, you can activate the router via router.activate. An example configuration is shown in the snippet below

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// in main.js
...
app.start().then(function(){
    // the router will use convention over configuration
    // to find views and view models
    // with the viewmodels/view folder structure
    router.useConvention();

    // when durandal finds a module, it replaces
    // viewmodel with view to located the view 
    // associated to a view model
    // viewmodels/todolist -> views/todolist
    // By default it will assume that view models
    // views and partial views are located in the
    // viewmodels and views folders respectively
    viewLocator.useConvention();

    // activate the router and set todolist as default
    router.activate('todolist');

    app.setRoot('viewmodels/shell');

    // override what happens when a bad route is
    // selected
    router.handleInvalidRouter = function(route, params){
        Console.log('Route not found');
    });
});
...


// in shell.js
...
function activate(){
    ...
    var routes = {/* ... */};
    router.map(routes);
    // activate router that returns a promise
    return router.activate('todolist');
}
Composing Views with Router Bindings

Durandal offers you the possibility of binding html elements directly to the router. For instance, to show a navigation bar with the different visible routes you can use:

1
2
3
4
5
6
7
8
9
10
11
<!-- the navigation ui -->
<div data-bind="foreach: router.visibleRoutes">
    <a data-bind="css: {active: isActive}
                  attr: {href: hash},
                  html: name"></a>
</div>

<!-- when navigating we can show a loading icon -->
<div data-bind="css: {active: router.isNavigating }">
    <!-- loading icon -->
</div>

When we want to use the router to select which view to display to the user, instead of explicitely stating which view to use we need to use the compose binding in a different way as we did before. See below:

1
2
3
4
5
6
7
8
9
<!-- instead of -->
<section data-bind="compose: {model: 'todolist', activate: true}">
</section>

<!-- we wire compose to the router -->
<section data-bind="compose: {
    model: router.activeItem,
    afterCompose: router.afterCompose }">
</section>

Resources

Interesting Tooling

Comments