Book Image

KNOCKOUTJS BLUEPRINTS

By : Carlo Russo
Book Image

KNOCKOUTJS BLUEPRINTS

By: Carlo Russo

Overview of this book

Table of Contents (12 chapters)
KnockoutJS Blueprints
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Index

The magic of KnockoutJS unveiled


We saw that all the magic of KnockoutJS starts with the call to:

ko.applyBindings(myViewModel);

This function gets two parameters: a View Model and a DOM element. You can skip the second parameter and it will default to the document.body.

First of all, it takes the View Model, and makes a ko.bindingContext from the View Model.

BindingContext tracks all the following information:

  • $parent: This is the View Model of the parent context; for example, every binding inside a foreach binding will have the foreach view model as $parent

  • $parents: This refers to an array with all the parents context; empty for the root View Model. You can use an indexer to traverse the hierarchy (for deep-nesting); for instance, $parents[1] will get you the 2nd ancestor and so on

  • $root: This is the View Model of the highest parent; itself for the root view model.

  • $rawData: This is the original View Model, before unwrapping (to understand "unwrapping" better, imagine that you have a property, x = ko.observable(12), and you execute x(); you are unwrapping the observable to get the value 12)

  • $data: This refers to the unwrapped View Model.

Then, it starts to apply the bindings to the node:

  • It stores the bindingContext inside the node data (but only if the current context is different from the context inside the parent node)

  • It checks if the current node contains the data-bind attribute, and applies the binding to each of them

  • For each binding, it executes the init function inside a call to ko.dependencyDetection.ignore, and then the update function inside a call to ko.dependentObservable; in this way, the update function of each binding handler works as a computed observable (more about computed observables a little later)

  • It executes these steps recursively for each descendant

    Note

    Binding to the same node more than once is not permitted; when you call ko.applyBindings it checks if the node is already bound and it will throw an exception.

    When you think you need to apply the binding again (maybe you changed the DOM structure without KnockoutJS) to the same node, the best idea is to rethink why you should do it; often you will see you can use the with binding handler to solve this problem in a KnockoutJS way.

    Or, if you are absolutely sure this is the best solution, you can use ko.cleanNode to reset the element to its previously unbound state.

The change of the bindingContext is done inside a few binding handlers (with, foreach, and so on) because they create a child bindingContext; you can do the same inside your custom binding handler's init function (for more information visit this URL: http://knockoutjs.com/documentation/custom-bindings-controlling-descendant-bindings.html).

Note

Before looking at a practical example, let's understand what a computed observable is.

ko.computed is the third kind of Observable KnockoutJS supports; it's defined by a function, and each time it runs it registers itself as subscriber of any Observable found during the evaluation.

This is the same method KnockoutJS uses for the binding handler you find in the View.

In a few words, a computed observable is an observer of another observer; the easiest example is the full name computed observable, defined as the concatenation of the observable, name, and the observable, last name:

var firstName = ko.observable("Bob"), 
    lastName = ko.observable("Smith");
var fullName = ko.computed(function() {
    return firstName() + " " + lastName(); 
});

The property fullName here gets evaluated each time one of its internal observables changes.

Let's understand step by step what happens when you execute ko.bindingHandler(viewModel) in the current document.

We start with the following DOM structure:

As the first step, it takes the document.body node to work on, as you can see in the following picture:

It creates and adds to the data of the node, body, a new BindingContext like this:

ko.bindingContext: {
   $root: obj,
   $rawData: obj,
   $parents: [],
   $data: obj
}

Here obj is the parameter, viewModel.

Then it walks inside the descendants searching for the data-bind attribute; the next node to work on is the following one:

Here it finds a foreach binding, so it executes the binding handler; the init function returns controlsDescendantBindings, so it stops descending.

The function init of foreach saves the descendants and clears the DOM structure, so now we have this structure:

After this step it ends, because all the descendants of document.body are bound to our view model.

When the code updates viewModel.jewels with the content of the category list, the flow continues.