Control-Flow Binding

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:

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:

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:

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

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:

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:

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:

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:

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:


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.

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:

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:


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.

Tags:

Comments

Related Articles