Advertisement
JavaScript & AJAX

Control-Flow Binding

by

As we’ve seen in previous lessons, designing a view for a ViewModel is like creating an HTML template for a JavaScript object. An integral part of any templating system is the ability to control the flow of template execution. The ability to loop through lists of data and include or exclude visual elements based on certain conditions makes it possible to minimize markup and gives you complete control over how your data is displayed.

We’ve already seen how the foreach binding can loop through an observable array, but Knockout.js also includes two logical bindings: if and ifnot. In addition, its with binding lets you manually alter the scope of template blocks.

This lesson introduces Knockout.js’ control-flow bindings by extending the shopping cart example from the previous lesson. We’ll also explore some of the nuances of foreach that were glossed over in the last lesson.


The foreach Binding

Let’s start by taking a closer look at our existing foreach loop:

<tbody data-bind='foreach: shoppingCart'>
  <tr>
    <td data-bind='text: name'></td>
    <td data-bind='text: price'></td>
    <td><button data-bind='click: $root.removeProduct'>Remove</button></td>
  </tr>
</tbody>

When Knockout.js encounters foreach in the data-bind attribute, it iterates through the shoppingCart array and uses each item it finds for the binding context of the contained markup. This binding context is how Knockout.js manages the scope of loops. In this case, it’s why we can use the name and price properties without referring to an instance of Product.


Working with Binding Contexts

Using each item in an array as the new binding context is a convenient way to create loops, but this behavior also makes it impossible to refer to objects outside of the current item in the iteration. For this reason, Knockout.js makes several special properties available in each binding context. Note that all of these properties are only available in the view, not the ViewModel.

The $root Property

The $root context always refers to the top-level ViewModel, regardless of loops or other changes in scope. As we saw in the previous lesson, this makes it possible to access top-level methods for manipulating the ViewModel.

The $data Property

The $data property in a binding context refers to the ViewModel object for the current context. It’s a lot like the this keyword in a JavaScript object. For example, inside of our foreach: shoppingCart loop, $data refers to the current list item. As a result, the following code works exactly as it would without using $data:

<td data-bind='text: $data.name'></td>
<td data-bind='text: $data.price'></td>

This might seem like a trivial property, but it’s indispensable when you’re iterating through arrays that contain atomic values like strings or numbers. For example, we can store a list of strings representing tags for each product:

function Product(name, price, tags) {
  this.name = ko.observable(name);
  this.price = ko.observable(price);
  tags = typeof(tags) !== 'undefined' ? tags : [];
  this.tags = ko.observableArray(tags);
}

Then, define some tags for one of the products in the shoppingCart array:

new Product("Buns", 1.49, ['Baked goods', 'Hot dogs']);

Now, we can see the $data context in action. In the <table> containing our shopping cart items, add a <td> element containing a <ul> list iterating through the tags array:

  <tbody data-bind='foreach: shoppingCart'>
      <tr>
        <td data-bind='text: name'></td>
        <td data-bind='text: price'></td>
        <td> <!-- Add a list of tags. -->
          <ul data-bind='foreach: tags'>
            <li data-bind='text: $data'></li>
          </ul>
        </td>
        <td><button data-bind='click: $root.removeProduct'>Remove</button></td>
      </tr>
    </tbody>
</table>

Inside of the foreach: tags loop, Knockout.js uses the native strings “Baked goods” and “Hot dogs” as the binding context. But, since we want to access the actual strings instead of their properties, we need the $data object.

The $index Property

Inside of a foreach loop, the $index property contains the current item’s index in the array. Like most things in Knockout.js, the value of $index will update automatically whenever you add or delete an item from the associated observable array. This is a useful property if you need to display the index of each item, like so:

<td data-bind='text: $index'></td>

The $parent Property

The $parent property refers to the parent ViewModel object. Typically, you’ll only need this when you’re working with nested loops and you need to access properties in the outer loop. For example, if you need to access the Product instance from the inside of the foreach: tags loop, you could use the $parent property:

<ul data-bind="foreach: tags">
  <li>
    <span data-bind="text: $parent.name"></span> - <span data-bind="text: $data"></span>
  </li>
</ul>

Between observable arrays, the foreach binding, and the binding context properties discussed previously, you should have all the tools you need to leverage arrays in your Knockout.js web applications.


Discounted Products

Before we move on to the conditional bindings, we’re going to add a discount property to our Product class:

function Product(name, price, tags, discount) {
  ...
  discount = typeof(discount) !== 'undefined' ? discount : 0;
  this.discount = ko.observable(discount);
  this.formattedDiscount = ko.computed(function() {
    return (this.discount() * 100) + "%";
  }, this);
}

This gives us a condition we can check with Knockout.js’ logical bindings. First, we make the discount parameter optional, giving it a default value of 0. Then, we create an observable for the discount so Knockout.js can track its changes. Finally, we define a computed observable that returns a user-friendly version of the discount percentage.

Let’s go ahead and add a 20% discount to the first item in PersonViewModel.shoppingCart:

this.shoppingCart = ko.observableArray([
  new Product("Beer", 10.99, null, .20),
  new Product("Brats", 7.99),
  new Product("Buns", 1.49, ['Baked goods', 'Hot dogs']);
]);

The if and ifnot Bindings

The if binding is a conditional binding. If the parameter you pass evaluates to true, the contained HTML will be displayed, otherwise it’s removed from the DOM. For instance, try adding the following cell to the <table> containing the shopping cart items, right before the “Remove” button.

<td data-bind='if: discount() > 0' style='color: red'>
  You saved <span data-bind='text: formattedDiscount'></span>!!!
</td>

Everything inside the <td> element will only appear for items that have a discount greater than 0. Plus, since discount is an observable, Knockout.js will automatically re-evaluate the condition whenever it changes. This is just one more way Knockout.js helps you focus on the data driving your application.

Figure 15: Conditionally rendering a discount for each product

You can use any JavaScript expression as the condition: Knockout.js will try to evaluate the string as JavaScript code and use the result to show or hide the element. As you might have guessed, the ifnot binding simply negates the expression.


The with Binding

The with binding can be used to manually declare the scope of a particular block. Try adding the following snippet towards the top of your view, before the “Checkout” and “Add Beer” buttons:

<p data-bind='with: featuredProduct'>
  Do you need <strong data-bind='text: name'></strong>? <br />
  Get one now for only <strong data-bind='text: price'></strong>.
</p>

Inside of the with block, Knockout.js uses PersonViewModel.featuredProduct as the binding context. So, the text: name and text: price bindings work as expected without a reference to their parent object.

Of course, for the previous HTML to work, you’ll need to define a featuredProduct property on PersonViewModel:

var featured = new Product("Acme BBQ Sauce", 3.99);
this.featuredProduct = ko.observable(featured);

Summary

This lesson presented the foreach, if, ifnot, and with bindings. These control-flow bindings give you complete control over how your ViewModel is displayed in a view.

It’s important to realize the relationship between Knockout.js’ bindings and observables. Technically, the two are entirely independent. As we saw at the very beginning of this series, you can use a normal object with native JavaScript properties (i.e. not observables) as your ViewModel, and Knockout.js will render the view’s bindings correctly. However, Knockout.js will only process the template the first time around—without observables, it can’t automatically update the view when the underlying data changes. Seeing as how this is the whole point of Knockout.js, you’ll typically see bindings refer to observable properties, like our foreach: shoppingCart binding in the previous examples.

Now that we can control the logic behind our view templates, we can move on to controlling the appearance of individual HTML elements. The next lesson digs into the fun part of Knockout.js: appearance bindings.

This lesson represents a chapter from Knockout Succinctly, a free eBook from the team at Syncfusion.

Related Posts
  • Code
    JavaScript & AJAX
    Animating KnockoutKnockoutjs
    Knockout.js is not an animation library. All of Knockout.js’ automatic updates are immediately applied whenever the underlying data changes. In order to animate any of its changes, we need to dig into Knockout.js’ internals and manually create animated transitions using another JavaScript framework like jQuery or MooTools. This lesson sticks with jQuery’s animation routines, but the concepts presented apply to other animation libraries as well.Read More…
  • Code
    JavaScript & AJAX
    Interactive BindingsKnockoutjs
    Form elements are the conventional way to interact with users through a webpage. Working with forms in Knockout.js is much the same as working with appearance bindings. But, since users can edit form fields, Knockout.js manages updates in both directions. This means that interactive bindings are two-way. They can be set programmatically and the view will update accordingly, or they can be set by the view and read programmatically.Read More…
  • Code
    JavaScript & AJAX
    Appearance BindingsKnockoutjs
    In the previous lesson, we saw how Knockout.js’ control-flow bindings provide a basic templating system for view code. Control-flow bindings provide the visual structure for your application, but a full-fledged templating system needs more than just structure. Knockout.js’ appearance bindings give you precise control over the styles and formatting of individual elements.Read More…
  • Code
    JavaScript & AJAX
    Knockout ObservablesKnockoutjs
    We’ve seen how observable properties let Knockout.js automatically update HTML elements when underlying data changes, but this is only the beginning of their utility. Knockout.js also comes with two more ways of exposing ViewModel properties: computed observables and observable arrays. Together, these open up a whole new world of possibilities for data-driven user interfaces.Read More…
  • Code
    JavaScript & AJAX
    Hello, KnockoutKnockoutjs
    This lesson is designed to be a high-level survey of Knockout.js’ main components. By implementing a concrete sample application, we’ll see how Knockout’s ViewModel, view, observables, and bindings interact to create a dynamic user interface.Read More…
  • Code
    JavaScript & AJAX
    Building Single Page Web Apps With Sinatra: Part 2Sinatra logo
    In the first part of this mini-series, we created the basic structure of a to-do application using a Sinatra JSON interface to a SQLite database, and a Knockout-powered front-end that allows us to add tasks to our database. In this final part, we'll cover some slightly more advanced functionality in Knockout, including sorting, searching, updating, and deleting.Read More…