Saturday, January 23, 2016

An introduction to knockout.js



Blog of Appliness

10 Things to Know About Knockout.js on Day One

Description: Description: knockout
The following tutorial was written by Ryan Niemeyer and appeared in the September issue of Appliness. You can find the original version on his blog at: knockmeout.net
Knockout.js is a JavaScript library that allows you to declaratively bind elements against model data with two-way updates happening automatically between your UI and model. While Knockout is quite easy to jump into, here are some areas that I feel are commonly misunderstood or overlooked by people that are just getting started with Knockout.

1 – How to set and read observables

Observables are functions that cache their actual value and subscribers internally. You read an observable’s value by calling it as a function with no arguments and you can set the observable to a new value by passing a single argument to it.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
var name = ko.observable(“Bob”); //initialize with a value
name(“Ted”); //set it to a new value
alert(name()); //read the value
Question: If you need to reference it as a function to read the value, then how come in a data-bind attribute you typically specify just the property name?
Answer: Most bindings call ko.utils.unwrapObservable on any values passed to them, which will safely return the value for both observables and non-observables. However, if you need to use an observable in an expression inside of your binding string, then you do need to reference it as a function, because it will be evaluated before the binding has access to it. Likewise, in your view model code, you typically need to reference your observables as functions, unless you actually want to pass the observable itself (not the value).
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<div data-bind=”visible: someFlag”>...</div>
 
<div data-bind=”visible: !someFlag()>...</div>

2 – The basic rules of computed observables

By default, the value of a computed observable is determined at the time of creation. However, this behavior can be controlled by creating the computed observable with an object that has a deferEvaluation property set to true.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
this.total = ko.computed({
    read: function() {
       var result = 0;
        ko.utils.arrayForEach(this.items(), function(item) {
            result += item.amount();
        });
    },
    deferEvaluation: true  //don’t evaluate until someone requests the value
}, this);
A computed observable will be re-evaluated whenever one of the observables that it accessed in its last evaluation changes. Dependency detection is done each time that the computed observable is evaluated. In the snippet below, if enabled is true, then it will not depend on disabledHelp. However, if enabled becomes false, then it will no longer depend on enabledHelp and will start depending on disabledHelp.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
//this computed observable will always depend on this.enabled and 
//will additionally depend on either this.enabledHelp or this.disabledHelp
this.helpText = ko.computed({
    return this.enabled() ? this.enabledHelp() : this.disabledHelp();
}, this);
A computed observable can also accept writes, if a write function is provided. Since, a computed observable doesn’t store a value itself, the job of the write function is to intercept the new value and decide how to update other observables, such that its own computed value will be appropriate.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
this.totalWithTax = ko.computed({
    read: function() {
        return this.total() * (1 + this.taxRate());
    },
    write: function(newValue) {
//do the opposite of the read function and set the total to the correct value
        this.total(newValue / (1 + this.taxRate()));
    }
}, this);

3 – An observableArray is just an extended observable

It is helpful to understand that observableArrays are actually just observables. They follow the same rules and have the same features as observables. However, they are also augmented with extra methods to perform basic array operations. These functions perform their action on the underlying array and then notify subscribers that there was a change. This included common operations like pop, push, reverse, shift, sort, splice, and unshift. In addition to the standard array operations, there are several other methods added to handle common tasks. These include remove, removeAll,destroy, destroyAll, replace, and indexOf.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
//remove an item
items.remove(someItem);
 
//remove all items with the name “Bob”
items.remove(function(item) {
    return item.name === “Bob”
});
//remove all items
items.removeAll();
 
//pass in an array of items to remove
items.removeAll(itemsToRemove)
 
//retrieve the index of an item
items.indexOf(someItem);
 
//replace an item
item.replace(someItem, replaceItem);
Note: destroy/destroyAll work like replace/replaceAll, except they mark the items with a_destroy property that is respected by the template and foreach bindings instead of actually removing them from the array.

4 – React to changes using manual subscriptions

Manual subscriptions give you a chance to programmatically react to a specific observable that changes. This is useful in many scenarios such as setting default values, interacting with non-Knockout code, and triggering AJAX requests. You are able to manually subscribe to observables, observableArrays, and computed observables.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
//trigger an AJAX request to get details when the selection changes
this.selectedItem.subscribe(function(newValue) {
    $.ajax({
        url:/getDetails’,
        data: ko.toJSON({
            id: newValue.id
        }),
        datatype: “json”,
        contentType: “application/json charset=utf-8”,
        success: function(data) {
            this.details(data.details);
        }
    });
}, this);

5 – Templates are flexible

The template binding is quite flexible. Here are a few ways that you might want to use it:
The template binding accepts a data argument that allows you to control the context of the binding. This is handy for simplifying references to nested content. It also accepts an if parameter that helps handle cases when the observable value may be null, so you do not generate errors from binding against properties of an undefined object.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<div data-bind=”template: { name: ‘nestedTmpl’,if: myNestedObject, data: myNestedObject }></div>
The template binding also accepts a foreach parameter to loop through items in the array passed to it. If the array is observable and changes, then Knockout efficiently adds or removes DOM nodes appropriately rather than re-rendering the nodes for the entire array.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<ul data-bind=”template: { name: ‘itemTmpl’, foreach: items }></ul>
You can even dynamically determine which template should be used to render your data.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<ul data-bind=”template: { name: getTemplate, foreach: items }></ul>
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
function getTemplate(item) {
    return item.readOnly() ? “viewOnly” : “editable”;
};
Finally, there are a number of callbacks supported by the template binding including afterRender,afterAdd, and beforeRemove. These hooks provide you with the affected elements and their associated data allowing you to perform actions like animating items that are being added or removed from an observableArray.

6 – Control-flow bindings are wrappers to the template binding

The control-flow bindings (foreach, if, ifnot, and with) are really wrappers to the template binding. Rather than pulling their content from a named template, they instead save off the children of the element to use as the “template” each time that a change is detected. These bindings help to simplify your markup and are especially useful in scenarios where you do not need to reuse the template content in other areas of your page.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<ul data-bind=foreach: items”>
    <li data-bind=”text: name”></li>
</ul>
 
<div data-bind=if: nestedObject”>
    <div data-bind=”text: nestedObject().value”
</div>
 
<div data-bind=”with: nestedObject”>
    <div data-bind=”text: value”></div>
</div>
Knockout also provides a comment-based syntax that allows you to use these bindings without a parent element. This is particularly useful in scenarios where adding another level is inappropriate, like in a list that contains static and dynamic content.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<ul>
    <li>Static Content</li>
    <!-- ko foreach: items -->
    <li data-bind=”text: name”></li>
    <!-- /ko -->
</ul>
 
<!-- ko if: editable -->
<button data-bind=”click: save”>Save</button>
<!-- /ko -->
 
<!-- ko with: nestedObject -->
<div data-bind=”text: value”></div>
<!-- /ko -->

7 – Bindings are aware of the current context

Inside of your bindings, Knockout provides a number of useful special variables related to the currentcontext.
$data – this is the current data bound at this scope.
$root – this is the top-level view model.
$parent – this is the data bound one scope level up from the current data.
$parents – this is an array of parent view models up to the top-level view model. $parents[0] will be the same as $parent.
$parentContext – this provides the context (object containing these special variables) of the parent scope.
$index – in the foreach binding, this is an observable representing the current array item’s index.
These special variables make it easy to bind against data or call methods from a higher scope without requiring references to these scopes on your view model itself.

8 – Keeping track of “this”

Knockout parses the data-bind attribute value on an element and turn it them into a JavaScript object that is used to process the bindings. In the case of event handlers, by the time that Knockout has parsed the binding string, it is dealing with function references that have no implicit value of this. When executing these handlers, such as the ones bound through the event and click bindings, Knockout sets the value of this equal to the current data being bound. This may not always be the appropriate context for your scenario, especially when executing a function that lives in a parent scope.
Suppose, our overall view model has a method that deletes an item:
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
var ViewModel = function() {
    this.items = ko.observableArray();
    this.deleteItem = function(item) {
       this.items.remove(item);
    };        
};
If I use this method on a click binding within a foreach loop through an array of items, then this will be set to my individual array items and not the overall view model.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<ul data-bind=foreach: items”>
    <li>
        <span data-bind=”text: name”></span>
        <a href=#” data-bind=”click: $root.deleteItem”> x </a>
    </li>
</ul>
Conveniently, Knockout also passes the current data as the first argument to any handlers called from the event and click bindings. This means that if we can control the value of this, then we will have both values that we need to perform an action on the array item from its parent. We want the function to execute with this as the parent and receive the array item as the first argument. There are several ways to create your view model in a way that ensures an appropriate value of this when functions are executed. Here are a couple of the most common ways:
Knockout does provide an implementation of bind that can be used on any function to create a wrapper that does guarantee the context. In this case, it would look like:
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
this.deleteItem = function(item) {
   this.items.remove(item);
}.bind(this);
Alternatively, you can save the correct value of this in a variable and reference it from within the function like:
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
var ViewModel = function() {
    var self = this;
    this.items = ko.observableArray();
    this.deleteItem = function(item) {
       self.items.remove(item);
    };        
};
Additionally, manual subscriptions and computed observables do take in a second argument to take care of this for you by controlling the value of this when they are executed.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
viewmodel.fullName = ko.dependentObservable(function() {
    return this.firstName() + “ “ + this.lastName();
}, viewmodel);
 
viewmodel.gratuityAdded.subscribe(function(newValue) {
    if (newValue) {
       this.total(this.total() * 1.15);
    }
}, viewmodel);

9 – Custom bindings need not be a last resort

There seems to be a slight misconception that custom bindings should only be considered if there is no other way to accomplish the desired functionality with the default bindings. Custom bindings are a powerful extensibility point that can be used in a variety of scenarios and should be considered one of the normal tools that are available to you along with observables, computed observables, and manual subscriptions. Besides helping control custom behavior and/or interacting with 3rd party components, they can also be used to simplify your bindings by encapsulating multiple behaviors.
A simple custom binding to start with is one that wraps an existing binding.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
ko.bindingHandlers.fadeInText = {
    update: function(element, valueAccessor) {
        $(element).hide();
        ko.bindingHandlers.text.update(element, valueAccessor);
        $(element).fadeIn(‘slow’);
   }
};
Whenever you find that your view model code is starting to reference DOM elements, then you will likely want to consider isolating this code in a custom binding. Given the element, your data, and the values passed to the binding, you can easily make one or two-way connections between your view model and the UI.

 10- ko.toJSON has multiple uses

ko.toJSON is a utility function used to convert objects that include observables to a JSON string. It first creates a clean JavaScript object (you can use ko.toJS to only take it this far), then it callsJSON.stringify on the clean object. Typically, you will be using this technique to package your data for sending back to the server.
It can also be very useful for debugging purposes. You can place a pre tag at the bottom of your page and display some or all of your view model using ko.toJSON to get a nicely formatted, live preview of how changes in your UI are affecting the underlying data. No need for console.log calls or alerts. Note that the second and third arguments to ko.toJSON are passed through to JSON.stringify.
Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/code.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/printer.png Description: Description: http://d3lyjx0kn8fidq.cloudfront.net/wp-content/plugins/wp-synhighlight/themes/default/images/info.gif 
<hr />
<h2>Debug</h2>
<pre data-bind=”text: ko.toJSON($root, null, 2)></pre>



No comments:

Post a Comment