Knockout Part 2: Scope

One of the more common errors that Knockout JS raises is related to scope.  Larger, more complex hierarchies need to be aware of the current level of scope, the complexity of the view model, and the current binding context.  We saw previously the use of simple, direct bindings; now we’ll look at some of the more advanced bindings, and shortcut pointers used to reference the scope hierarchy.  A list of the bindings we’ll use is:

  • for-each – Similar to a for each loop in C# or VB, this mechanism loops through an array of objects.
  • with – Redefines the scope to be more narrow; provides an object that’s defines as the new scope for the current subset of elements.
  • $root – A reference to the root view model.
  • $parent – A reference to the direct parent.
  • $parents – A collection of all the parents, which can be accessible via index (ie. $parents[0])

Let’s look at a more complex view model used for the provided sample.

var viewModel = {
results: [
{
isAvailable: true,
product:{ name: “GTX 1000 Laser Pointer”, price: “$39.99”, description: “A one of a kind item! Serves as a laser pointer with an added pen built in. Side button toggles color between red, green, and blue.” },

canPurchase: function(item) { return “Yes”; }
},
.

.
],
canPurchaseItem: function(item) {
return item.isAvailable == true ? “Yes” : “No”;
}
};

ko.applyBindings(viewModel);

Our view model has a results property, which is an array of objects.  Each object has a product, a complex object of itself, and a method named canPurchase for determining whether the product is available.  Additionally, the view model has a canPurchaseItem method that will be referred to within a scope hierarchy.

I think this example can be best illustrated by taking each piece individually.  The first knockout reference appears below.

<div data-bind=”foreach:results”>

The results array is bound to the root div.  Within it is a template of elements, which only the beginning of the template appears below.

<table>
<thead></thead>
<tbody data-bind=”with:product”>

The table is currently bound to an item within the results array.  When we get to the tbody element, the scope is reclassified to now point to the product property.  This means the scope no longer directly knows about the original bound object.  However, we haven’t quite lost it, as we see in the next example.

<tr>
<td><span data-bind=”text:$data.price” /></td>
<td><span data-bind=”text:$parent.isAvailable” /></td>
<!– parents bubbles up to root view model –>
<td><span data-bind=”text:$parents[0].canPurchase($data)” /></td>
<!– root refers to view model – $parent refers to root result, whereas $data would refer to product –>
<td><span data-bind=”text:$root.canPurchaseItem($parent)” /></td>
</tr>

The following template appears within the tbody and is scoped to the product.  Remember that isAvailable property?  This property is scoped with the item in the array, one level above the product object.  If we hadn’t used a with statement, it could be directly referred to.  But all is not lost; the $parent reference is a pointer to said object.  The $parent object is a pointer to that parent view model reference.  We can also use the $parents[0] in the same way that $parent is used, or even use $parents[X] to drill even higher up the hierarchy.

Lastly, if we need to refer to something within the view model, the $root reference is a pointer to that object.  Here we can call that method I briefly referred to above.

Scope binding is one of the harder concepts to grasp and can be the most frustrating when learning Knockout.   It’s important to understand how certain bindings may cause a scope change.  Note that the following error is one of the more common errors caused by errors in bindings.  If you happen to get the following error, it’s often because a reference to scope is incorrect.  Here I changed the code to try to find canPurchaseItem in the $parent instead of $root, and it rightly blows up.

Unable to parse bindings.
Message: TypeError: $parent.canPurchaseItem is not a function;
Bindings value: text:$parent.canPurchaseItem($parent)

I hope this helps you understand Knockout scope and more complex bindings like loops.

Advertisements

One thought on “Knockout Part 2: Scope

  1. Pingback: Knockout Part 3: Events | On All Things Web

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s