Book Image

Mastering KnockoutJS

By : Timothy Moran
Book Image

Mastering KnockoutJS

By: Timothy Moran

Overview of this book

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

The data-bind syntax


Knockout takes advantage of the HTML5 data-* attribute specification to define its data-bind attribute. Though all HTML attributes are necessarily strings, Knockout parses them as name:value pairs. The name refers to the binding handler to be used and the value refers to the value the binding will use:

<button data-bind="enable: canSave">Save</button>

The data-bind attribute can also contain multiple bindings separated by commas. This allows multiple properties to be bound on an element:

<input data-bind="value: firstName, enable: canEdit" />

In the preceding example, the enable binding uses canEdit as a value. The binding will set the disabled attribute on the button element when canEdit is false, and remove the disabled attribute when canEdit is true. If canEdit is an observable, the enable binding will update whenever canEdit is updated. If canEdit is a literal value, such as true, it will only use the value to set the initial state.

Enable is a one-way binding; it will update the element with changes from the value but it will not update the value with changes from the element. This is because when enable is being used to control the element, Knockout assumes that nothing will be programmatically updating the element. Updates should happen in the viewmodel, and binding handlers should be responsible for ensuring the view is kept in sync.

When users update the UI of data-bound input elements, those changes need to be synced to the viewmodel. This is done with two-way bindings, such as the value binding:

<input data-bind="value: firstName" />

This binding will set the initial value of the input element to the current value of the firstName property, and after that, it will ensure that any changes to either the element's value or the property cause the other to update. If the user types something into the input, the firstName property will receive the value. If the firstName property is updated programmatically, the input's value will be updated.

These are both examples of binding against a simple property on the viewmodel. This is the most common case, but Knockout supports more complex scenarios as well.

Note

For a complete list of the standard Knockout binding handlers, see the Knockout documentation (http://knockoutjs.com/documentation/introduction.html).

Binding with nested properties

In the previous example, Knockout parsed the binding value for the name of a property and looked for that property on the current viewmodel. You can also provide deep property references. Consider the following object:

var viewmodel = {
  user: {
    firstName: ko.observable('Tim'),
    age: ko.observable(27)
  }
};

We can bind directly against the firstName property of the viewmodel's user by using standard dot notation:

<input data-bind="value: user.firstName" />

Binding against functions

If you are using the click or event bindings to bind some UI event, the binding expects the property to be a function. Functions will receive the current model (the binding context) as their first parameter, and the JavaScript event as the second parameter (though you shouldn't need to do this very often).

In this example, the parent viewmodel receives the contact to be removed from the click binding because the foreach loop creates a nested binding context for each contact. The parent reference in the binding moves the context up to the parent viewmodel to get access to the remove function:

<ul data-bind="foreach: contacts">
    <li>
      <span data-bind="text: name"></span>
      <button data-bind="click: $parent.remove">Remove</button>
    </li>
</ul>

var ViewModel = function() {
    var self = this;
    self.contacts = ko.observableArray([{ name: 'Tim' }, { name: 'Bob' }]);
    self.remove = function (contact) {
         self.contacts.remove(contact);
    };
};

Binding with expressions

In addition to property references, Knockout also supports the use of JavaScript expressions as binding values. For bindings that expect true or false values, such as enable, we can use Boolean expressions to set them:

<button data-bind="enable: age > 18">Approve</button>

We can also use ternary expressions to control the result of the expression. This is useful in cases where Booleans are not expected, such as text bindings:

Old enough to Drink in the U.S. 
<span data-bind="text: age > 18 ? 'Yes' : 'No'"></span>

Now the span will have Yes as content.

Both forms of expressions will use dependency tracking to rerun if they read from an observable the first time they are run. If age was an observable value, we could update it and the element's binding would re-evaluate the expression, changing the text or enabled state if the result changed.

Binding with function expressions

The last method to set binding values is by using functions. You can call a function by referencing it in the binding:

<button data-bind="enable: canApprove(age)">Approve</button>

You can also write an anonymous function as a string directly in the binding. When creating a function for the click binding, the parameters are the binding context (viewmodel) and the JavaScript click event. If you bind against a viewmodel function using its property name, it would receive the same parameters:

<button data-bind="text: 
function(data) { console.log(data.age)  }">Log Age</button>

Though this is possible, I wouldn't encourage it. It places logic directly in the view instead of in the viewmodel where it belongs. You should only use this last method in very special cases. It's much better to place the method on the viewmodel and just use a property reference.

Using parentheses in bindings

It can be confusing trying to figure out when to use parentheses in bindings to use an observable as a value. Knockout tries to be helpful by not requiring the parentheses in simple binding expressions like this one:

<input data-bind="value: firstName" />

In this example, the firstName property could be either an observable or a literal value, and it would work just fine. However, there are two cases when the parentheses are needed in bindings: when binding against a nested property and when binding with an expression. Consider the following viewmodel:

var viewmodel = {
  user: ko.observable({
    firstName: ko.observable('Tim'),
    age: ko.observable(27)
  })
};

The user object here is an observable property, as are each of its properties. If we wanted to write the same binding now, it would need to include parentheses on the user function but still not on the firstName property:

<input data-bind="value: user().firstName" />

In cases where we are binding directly against a property, the parentheses of that property are never needed. This is because Knockout is smart enough to understand how to access the value of the observable that it is given in bindings.

However, if we are binding against an expression, they are always needed:

<button data-bind="enable: user().age > 18">Approve</button>
<button data-bind="enable: user().age() > 18">Approve</button>

Neither of these bindings will cause errors, but the first one will not work as expected. This is because the first expression will try to evaluate on the age observable itself (which is a function, not a number) instead of the observable's value. The second one correctly compares the value of the observable to 18, producing the expected result.

Debugging with ko.toJSON

Because ko.toJSON accepts the spaces argument for JSON.stringify, you can use it in a text binding to get a live copy of your viewmodel with nice, readable formatting:

<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

The cp1-databind branch has an interactive example of each of these bindings.