A Beginners Guide to KnockoutJS: Templating and More
There are four control-flow bindings: foreach
, if
, ifnot
and with
. These control bindings allow you to declaratively define the control-flow logic without creating a named template as you will see below.
The foreach
binding duplicates a section of markup for each entry in an array, and binds each copy of that markup to the corresponding array item. This is suitable for rendering lists or tables. If your array is an observable array, whenever you later add or remove array entries, the binding will update the UI to match by inserting or removing more copies of the list items or table rows, without affecting any other DOM elements. See the following example:
<table> <thead> <tr><th>Title</th><th>Author</th></tr> </thead> <tbody data-bind="foreach: books"> <tr> <td data-bind="text: title"></td> <td data-bind="text: author"></td> </tr> </tbody> </table> <script type="text/javascript"> function viewModel() { var self = this; self.books = ko.observableArray([ { title: 'The Secret', author: 'Rhonda Byrne' }, { title: 'The Power', author: 'Rhonda Byrne' }, { title: 'The Magic', author: 'Rhonda Byrne' } ]); } ko.applyBindings(new viewModel()); </script>
Here, a table row will be created automatically for each array entry in the books array.
Sometimes you may need to refer to the array entry itself rather than just one of its properties. In that case, you can use the pseudovariable $data
. It means "the current item", when is used within a foreach
block.
<ul data-bind="foreach: daysOfWeek"> <li> <span data-bind="text: $data"></span> </li> </ul> <script type="text/javascript"> function viewModel() { var self = this; self.daysOfWeek = ko.observableArray([ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]); }; ko.applyBindings(new viewModel()); </script>
This will list all days of the week without need to repeat the code for each item separately.
In Knockout you can nest as many control-flow bindings as you wish. And when you do that, it’s often desirable to reach back up the hierarchy and access data or functions from parent contexts. In such cases you can use the following pseudovariables:
$parent
– represents the data item outside the current foreach
block
$parents
– is an array representing data items from all outer control-flow scopes. $parents[0]
is the same as $parent
. $parents[1]
represents the item from the grandparent control-flow scope, and so on.
$root
– represents the item from the outer-most control-flow scope. Typically this is your top-level view model object.
In the following example we use the $parent
pseudovariable in order to remove properly a book item from the books array:
<table> <thead> <tr><th>Title</th><th>Author</th></tr> </thead> <tbody data-bind="foreach: books"> <tr> <td data-bind="text: title"></td> <td data-bind="text: author"></td> <td><a href="#" data-bind="click: $parent.removeBook">Remove</a></td> </tr> </tbody> </table> <script type="text/javascript"> function viewModel() { var self = this; self.books = ko.observableArray([ { title: 'The Secret', author: 'Rhonda Byrne' }, { title: 'The Power', author: 'Rhonda Byrne' }, { title: 'The Magic', author: 'Rhonda Byrne' } ]); self.removeBook = function() { self.books.remove(this); } } ko.applyBindings(new viewModel()); </script>
In some cases, you might want to duplicate a section of markup, but you don’t have any container element on which to put a foreach binding. Then you can use the following syntax:
<ul> <li><strong>Days of week:</strong></li> <!-- ko foreach: daysOfWeek --> <li> <span data-bind="text: $data"></span> </li> <!-- /ko --> </ul> <script type="text/javascript"> function viewModel() { var self = this; self.daysOfWeek = ko.observableArray([ 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday' ]); }; ko.applyBindings(new viewModel()); </script>
In this example, you can’t use a normal foreach
binding. If you put it on the <ul>
this will duplicate the header item, and if you want to put a further container inside the <ul>
you can’t because only <li>
elements are allowed inside <ul>
s. The solution is to use the containerless control-flow syntax where the <!-- ko -->
and <!-- /ko -->
comments define a "virtual element" that contains the markup inside, which syntax Knockout understands and binds this virtual element as if you had a real container element. This type of syntax is also valid for if
and with
bindings.
The if
binding causes a section of markup to appear in your document, only if a specified expression evaluates to true. Then the contained markup will be present in the document, and any data-bind attributes on it will be applied. On the other hand, if your expression evaluates to false, the contained markup will be removed from your document without first applying any bindings to it.
<label><input type="checkbox" data-bind="checked: showList" />Show me list</label> <ul data-bind="if: showList"> <li>Item</li> <li>Item</li> <li>Item</li> </ul> <script type="text/javascript"> function viewModel() { var self = this; self.showList = ko.observable(false); } ko.applyBindings(new viewModel()); </script>
The with
binding creates a new binding context, so that descendant elements are bound in the context of a specified object. The object that you want to use as the context for binding descendant elements. If the expression you supply evaluates to null or undefined, descendant elements will not be bound at all, but will instead be removed from the document. The with
binding changes the data context to whatever object you specify. This is especially useful when dealing with object graphs with multiple parent/child relationships.
<p data-bind="text: book"> </p> <ul data-bind="with: details"> <li>Category: <span data-bind="text: category"> </span></li> <li>Author: <span data-bind="text: author"> </span></li> <li>Publisher: <span data-bind="text: publisher"> </span></li> </ul> <script type="text/javascript"> function viewModel() { var self = this; self.book = ko.observable('The Secret'); self.details = ko.observable({category: 'Psychology', author: 'Rhonda Byrne', publisher: 'Simon & Schuster Ltd'}); } ko.applyBindings(new viewModel()); </script>
Templating
The template
binding populates the associated DOM element with the results of rendering a template. Templates are a simple and convenient way to build sophisticated UI structures – possibly with repeating or nested blocks – as a function of your view model data. There are two main ways of using templates. The first one, native templating, is the mechanism that underpins foreach
, if
, with
, and other control-flow bindings. Internally, those control-flow bindings capture the HTML markup contained in your element, and use it as a template to render against an arbitrary data item. This feature is built into Knockout and doesn’t require any external library. You can see the basic scheme for creating a template here:
<div data-bind="template: 'myTemplate'"></div> <script type="text/html" id="myTemplate"> // template code here </script>
In the following example you can see how to use it in action:
<div data-bind="template: 'book-template'"></div> <script type="text/html" id="book-template"> <h3 data-bind="text: title"></h3> <p>Written by: <span data-bind="text: author"></span></p> </script> <script type="text/javascript"> function viewModel() { var self = this; self.title = ko.observable('The Secret') self.author = ko.observable('Rhonda Byrne') } ko.applyBindings(new viewModel()); </script>
Here, we must use an id equals to the template name in order to bound the template to the rest of our markup. In this case it is ‘book-template’.
Instead of using the short syntax described above, we can pass more parameters to the template binding, which will gives us more precise control over the final output.
//syntax: <div data-bind="template: { name: 'myTemplate', data: myData, afterRender: myLogic }"></div> <div data-bind="template: { name: 'book-template', data: bestseller, afterRender: msg }"></div> //template here <script type="text/javascript"> function MyViewModel() { var self = this; self.bestseller = { title: 'The Secret', author: 'Rhonda Byrne' }; self.ordinary = {title: 'Some Name', author: 'Some Author'}; self.msg = function(elements) { alert('Hip Hip Hooray!!! :)'); } } ko.applyBindings(new MyViewModel()); </script>
Here, the name
is the id of the element that contains the template you wish to render; the data
is an object to supply as the data for the template to render; and the afterRender
is a callback function to be invoked against the rendered DOM elements.
The following example is an equivalent of a foreach
binding. Here, foreach
is passed as a parameter to the template
binding.
//syntax: <div data-bind="template: { name: 'myTemplate', foreach: myArray }"></div> <div data-bind="template: { name: 'book-template', foreach: books }"></div> //template here <script type="text/javascript"> function MyViewModel() { var self = this; self.books = [ { title: 'The Secret', author: 'Rhonda Byrne' }, { title: 'The Power', author: 'Rhonda Byrne' } ] } ko.applyBindings(new MyViewModel()); </script>
You can get exactly the same result by embedding an anonymous template directly inside the element to which you use foreach binding:
<div data-bind="foreach: books"> <h3 data-bind="text: title"></h3> <p>Written by: <span data-bind="text: author"></span></p> </div>
The second way of using templates is to connect Knockout to a third-party template engine. Knockout will pass your model values to the external template engine and inject the resulting markup string into your document. For examples that use the jquery.tmpl and Underscore template engines check the documentation.
Extending Observables
Knockout observables provide the basic features necessary to support reading/writing values and notifying subscribers when that value changes. In some cases, though, you may wish to add additional functionality to an observable like adding additional properties to the observable. Knockout extenders provide an easy and flexible way to do just that.
Creating an extender involves adding a function to the ko.extenders
object. The function takes in the observable itself as the first argument and any options in the second argument. It can then either return the observable or return something new like a computed observable that uses the original observable in some way.
Now we’ll create an observable extender which will add the ability to show a hint message.
<input data-bind='value: name, hasfocus: name.on' /> <span data-bind="visible: name.on, text: name.hint"></span> <br /> <input data-bind='value: pass, hasfocus: pass.on' /> <span data-bind="visible: pass.on, text: pass.hint"></span> <script type="text/javascript"> // begin observable extender ko.extenders.hints = function(target, hint) { target.on = ko.observable() target.hint = ko.observable() function showHint(value){ target.on(value ? false : true); target.hint(value ? "" : hint); } showHint(target()); return target; }; // end observable extender function viewModel() { var self = this; self.name = ko.observable().extend({hints: 'Type your name here'}) self.pass = ko.observable().extend({hints: 'Type your password here'}) }; ko.applyBindings(new viewModel()); </script>
Custom Bindings
Knockout’s built-in bindings allow you to handle most binding scenarios, but if you encounter a specialized binding scenario that isn’t covered, you can create custom bindings with Knockout which gives you a lot of flexibility to encapsulate sophisticated behaviors in an easy-to-reuse way. For example, you can create interactive components like grids, tabsets, and so on, in the form of custom bindings.
Knockout bindings consist of two methods: init
and update
. Creating a binding is as simple as creating an object with these two methods and registering that object with Knockout using ko.bindingHandlers
as shown below.
ko.bindingHandlers.yourBindingName = { init: function(element, valueAccessor, allBindingsAccessor, viewModel) { }, update: function(element, valueAccessor, allBindingsAccessor, viewModel) { } }; // once created, you can use your custom binding in similar way as any built-in binding <div data-bind="yourBindingName: someValue"> </div>
The init
function will only run the first time that the binding is evaluated for this element. This is usually used to run one-time initialization code or to wire up event handlers that let you update your view model based on an event being triggered in your UI.
The update
function provides a way to respond when associated observables are modified. Typically, this is used to update your UI based on changes to your view model.
The init
and update
functions are supplied four parameters. Generally, you will want to focus on the element
and the valueAccessor
parameters, as they are the standard way to link your view model to your UI. You don’t actually have to provide both init
and update
callbacks – you can just provide one or the other if that’s all you need.
The element
parameter gives you direct access to the DOM element that contains the binding.
The valueAccessor
parameter is a function that gives you access to what was passed to the binding. If you passed an observable, then the result of this function will be that observable (not the value of it). If you used an expression in the binding, then the result of the valueAccessor
will be the result of the expression.
The allBindingsAccessor
parameter gives you access to all of the other bindings that were listed in the same data-bind attribute. This is generally used to access other bindings that interact with this binding. These bindings likely will not have any code associated with them and are just a way to pass additional options to the binding, unless you choose to pass an object with multiple properties into your main binding. For example, optionsValue
, optionsText
, and optionsCaption
are bindings that are only used to pass options to the options
binding.
The viewModel
parameter will provides access to your overall view model for bindings outside of templates. Inside of a template, this will be set to the data being bound to the template. For example, when using the foreach
option of the template binding, the viewModel
parameter would be set to the current array member being sent through the template. Most of the time the valueAccessor
will give you the data that you want, but the viewModel
parameter is particularly useful if you need an object to be your target when you call/apply functions.
In the following example we will create a custom binding which scale a textarea when it is in focus.
<textarea data-bind="scaleOnFocus: scaleArea, scaleUp: {height: '200', width: '400'}, scaleDown: {height: '15', width: '150'}"></textarea> <script type="text/javascript"> // begin custom binding ko.bindingHandlers.scaleOnFocus = { init: function(element, valueAccessor) { $(element).focus(function() { var value = valueAccessor(); value(true); }); $(element).blur(function() { var value = valueAccessor(); value(false); }); }, update: function(element, valueAccessor, allBindingsAccessor) { var value = valueAccessor(); var allBindings = allBindingsAccessor(); var up = allBindings.scaleUp; var down = allBindings.scaleDown; if (ko.utils.unwrapObservable(value)) $(element).animate(up); else $(element).animate(down); } }; // end custom binding function viewModel() { var self = this; self.scaleArea = ko.observable() }; ko.applyBindings(new viewModel()); </script>
First, in the init
function we declare that when element is in focus then its value will be set to true, and vice versa. Then in the update
function we use allBindingAccessor
parameter to add additional options to our binding – scaleUp
and scaleDown
. We use the ko.utils.unwrapObservable
to get the current binding’s value and check if it is set to true. If so, the DOM element is scaled up, otherwise it is scaled down.
At last let’s see an example that combines the hints observable extender and scaleOnFocus custom binding:
<input data-bind='value: name, hasfocus: name.on' /> <span data-bind="visible: name.on, text: name.hint"></span> <br /> <input data-bind='value: email, hasfocus: email.on' /> <span data-bind="visible: email.on, text: email.hint"></span> <br /> <textarea data-bind="value: msg.hint, scaleOnFocus: scaleArea, scaleUp: {height: '200', width: '400'}, scaleDown: {height: '50', width: '150'}"></textarea> <script type="text/javascript"> ko.extenders.hints = function(target, hint) { target.on = ko.observable() target.hint = ko.observable() function showHint(value){ target.on(value ? false : true); target.hint(value ? "" : hint); } showHint(target()); return target; }; ko.bindingHandlers.scaleOnFocus = { init: function(element, valueAccessor) { $(element).focus(function() { var value = valueAccessor(); value(true); }); $(element).blur(function() { var value = valueAccessor(); value(false); }); }, update: function(element, valueAccessor, allBindingsAccessor) { var value = valueAccessor(); var allBindings = allBindingsAccessor(); var up = allBindings.scaleUp; var down = allBindings.scaleDown; if (ko.utils.unwrapObservable(value)) $(element).animate(up); else $(element).animate(down); } }; function viewModel() { var self = this; self.name = ko.observable().extend({hints: 'Type your full name'}) self.email = ko.observable().extend({hints: 'Type a valid email'}) self.msg = ko.observable().extend({hints: 'Leave a message...'}) self.scaleArea = ko.observable() }; ko.applyBindings(new viewModel()); </script>
You can place the hints observable and scaleOnFocus binding in a separate file and then including them in the main file. This makes the code modular and allows you to re-use it whenever you want.
That’s it, folks! I hope you enjoyed this series. Now you have all necessary knowledge to start and continue learning and experimenting with Knockout. For more comprehensive examples and tutorials you can go to the Knockout site, which I suggest you to do.