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

Control flow bindings


So far, we have looked at one-way and two-way bindings that set or sync data with an attribute on an HTML element. There is a different kind of binding that Knockout uses for modifying the DOM by adding or removing nodes. These are the control flow bindings, and they include foreach, if, with, and template.

All of the control flow bindings work by actually removing their content from the DOM and creating an in-memory template from it. This template is used to add and remove the content as necessary.

Control flow bindings (except if) also introduce a binding context hierarchy. Your root binding context is the viewmodel passed to ko.applyBindings. The data-bind attributes have access to properties in the current context. Control flow bindings (other than if) create a child-binding context, meaning that data-bind attributes inside the control flow binding's template have access to the properties of their context and not the root context. Bindings inside a child context have access to special properties to allow them to navigate the context hierarchy. The most commonly used are:

  • $parent: This accesses the binding context of the immediate parent. In this example, group and $parent.group refer to the same property because $parent accesses the context outside of the person:

    <span data-bind="text: group"></span>
    <div data-bind="with: person">
      <span data-bind="text: name"></span>
    <span data-bind="text: $parent.group"></span>
      </div>
  • $parents[n]: This is an array of parent contexts. The $parents[0] array is same as $parent.

  • $root: This is the root viewmodel, the highest context in the hierarchy.

  • $data: This is the current viewmodel, useful inside foreach loops.

    Note

    For a complete list of context properties, see the Knockout documentation for them at http://knockoutjs.com/documentation/binding-context.html.

The if binding

The if binding takes a value or expression to evaluate and only renders the contained template when the value or expression is truthy (in the JavaScript sense). If the expression is falsy, the template is removed from the DOM. When the expression becomes true, the template is recreated and any contained data-bind attributes are reapplied. The if binding does not create a new binding context:

<div data-bind="if: isAdmin">
  <span data-bind="text: user.username"></span>
  <button data-bind="click: deleteUser">Delete</button>
</div>

This div would be empty when isAdmin is false or null. If the value of isAdmin is updated, the binding will re-evaluate and add or remove the template as necessary.

There is also an ifnot binding that just inverts the expression. It's useful if you want to still use a property reference without needing to add a bang and parentheses. The following two lines are equivalent:

<div data-bind="if: !isAdmin()" >
<div data-bind="ifnot: isAdmin">

The parentheses are needed in the first example because it is an expression, not a property name. They are not needed in the second example because it is a simple property reference.

The with binding

The with binding creates a new binding context using the supplied value, which causes bindings inside the bound element to be scoped to the new context. These two snippets are functionally similar:

<div>
  First Name:
<span data-bind="text: selectedPerson().firstName"></span>
  Last Name:
<span data-bind="text: selectedPerson().lastName"></span>
</div>

<div data-bind="with: selectedPerson">
  First Name:
<span data-bind="text: firstName"></span>
  Last Name:
<span data-bind="text: lastName"></span>
</div>

While saving a few keystrokes and keeping your bindings easier to read is nice, the real benefit of the with binding is that it is an implicit if binding. If the value is null or undefined, the content of the HTML element will be removed from the DOM. In the cases where this is possible, it saves you from the need to make null checks for each descendant binding.

The foreach binding

The foreach binding creates an implicit template using the contents of the HTML element and repeats that template for every element in the array.

This viewmodel contains a list of people we need to render:

var viewmodel = {
  people: [{name: 'Tim'}, {name: 'Justin}, {name: 'Mark'}]
}

With this binding, we create an implicit template for the li element:

<ul data-bind="foreach: people">
  <li data-bind="text: name"></li>
</ul>

This binding produces the following HTML:

<ul>
  <li>Tim</li>
  <li>Justin</li>
  <li>Mark</li>
</ul>

The thing to note here is that the li element is binding against name, which is the property of a person. Inside the foreach binding, the binding context is the child element. If you need to refer to the child itself, you can either use $data or supply an alias to the foreach binding.

The $data option is useful when the array only contains primitives that you want to bind against:

var viewmodel = {
  people: ['Tim', 'Justin, 'Mark']
}
...
<ul data-bind="foreach: people">
  <li data-bind="text: $data"></li>
</ul>

The alias option can clean up your code, but it is particularly useful when you have a nested context and want to refer to the parent. Refer to the following code:

<ul data-bind="foreach: { data: categories, as: 'category' }">
    <li>
        <ul data-bind="foreach: { data: items, as: 'item' }">
          <li>
            <span data-bind="text: category.name"></span>:
            <span data-bind="text: item"></span>
          </li>
         </ul>
    </li>
</ul>

This can be achieved with $parent, of course, but it is more legible when using an alias.