Knockout Crash Course

A quick overview of Knockout.js

Before getting started with Falcon, let's need to run briefly through how Knockout.js works. If you already have a decent understanding of general Knockout concepts, feel free to skip this portion of the tutorial and get started with our Basic Todo List example. Additionally, if you're looking for more information on Knockout, be sure to check out their interactive tutorial and documentation. Nevertheless, let's get started.

What is Knockout?

Knockout.js is a Javascript library that provides a Model View ViewModel (MVVM) architecture for web applications. Essentially, it allows DOM elements to be dynamically updated and re-rendered when certain variables are updated without creating a hard dependency between your Javascript and HTML. In other words, Knockout allows us to update a value in Javascript and then have all of the related HTML elements (think form fields, paragraphs, css classes, etc.) update and re-rendered when needed.

Observables and Bindings

To begin we'll need to understand the two most core concepts of Knockout: Observables and Bindings.

Observables are Knockout's form of variables that are meant to hold and update data. An observable is created like so: var myVariable = ko.observable(). Each observable that you create returns an overloaded method that can be used to read/write data to/from the observable. For clarity, here's how you read data myVariable() and write data myVariable("Hello World") to an observable.

Bindings are the functions that Knockout uses to link observables with HTML elements. Essentially it's how Knockout links your javascript and HTML. Bindings on an HTML element are defined by a custom attribute data-bind="". Here's just a few examples of what bindings can be used for:

Additionally, it's possible to create your own, custom bindings, but we won't discuss that in these early tutorials. If you're interested in learning how, take a look at the Knockout documentation for custom bindings.

Take a look at the jsFiddle example below. Here we're creating an observable 'name' on a viewModel object. We're setting the name to 'Mr. Falcon' and then applying the bindings to our html. ko.applyBindings( viewModel ) is how we initialize Knockout and tell it to parse through our html to link bindings with the specified viewModel.

In the HTML tab of the jsFiddle you'll see our Knockout template. Here we have an input text field that will update the value of our 'name' observable using the 'value' binding when the text field's value is changed. Once the 'name' observable is updated, Knockout will also update the text in the 'span' element via the 'text' bindings. Take a look at the result tab to see how this works.

1 var viewModel = {};
2 viewModel.name = ko.observable();
3 
4 viewModel.name("Mr. Falcon");
5 
6 ko.applyBindings( viewModel );
Application Javascript
1 <div>
2     <label>Please enter your name:</label>
3     <input type="text" data-bind="value: name" />
4 </div>
5 Welcome, <span data-bind="text: name"></span>!
Application HTML

Computed Observables

In this next example we'll talk about Computed Observables. Computed observables are essentially calculated values based on other observables or computed observables. Computed observables can be created like so:

var myComputeObservable = ko.computed(function(){...})

The function that is defined in the computed should return the calculated value of the observable. If we've read any observable values from within the function then whenever that observable is updated, the computed observable will also be recalculated.

For instance, in the example below we have two observables that represent a first and last name of a user. We also have a computed observable called 'full_name' that will output the full name representation of whatever the user has entered for their first and last names. In the example, we've bound two input fields via the 'value' binding to the first and last name bindings. We then bind the 'full_name' computed observable to the text binding of the span field which will output the full name. Notice also that we've introduced the 'if' binding around our text output of the full name. The 'if' binding is used to either display or hide children elements based on the truthy or falsey value of an observable.

 1 var viewModel = {}
 2 viewModel.first_name = ko.observable()
 3 viewModel.last_name = ko.observable()
 4 viewModel.full_name = ko.computed(function(){
 5     var first_name = viewModel.first_name() || "";
 6     var last_name = viewModel.last_name() || "";
 7     var full_name = first_name + " " + last_name;
 8     
 9     //Trim any white space
10     full_name = full_name.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
11     
12     return full_name;
13 });
14 
15 ko.applyBindings( viewModel );
Application Javascript
 1 <div>
 2     <label>Please enter your first name:</label>
 3     <input type="text" data-bind="value: first_name" />
 4 </div>
 5 <div>
 6     <label>Please enter your last name:</label>
 7     <input type="text" data-bind="value: last_name" />
 8 </div>
 9 <div data-bind="if: full_name">
10     Welcome, <span data-bind="text: full_name"></span>!
11 </div>
Application HTML

Writeable Computed Observables and Virtual Bindings

In the previous example we created a computed observable that simply calculated and outputted a specific value. However, it is possible to also write to a computed observable as well. In this example, we'll be introducing Writeable Observables and Virtual Bindings (which is just fancy terminology for bindings that use HTML comment notation instead of the data-bind="" attribute).

In this example we update the 'full_name' computed binding to also become writeable. In our template, we've added another field that allows users to type in their full name (instead of just their first and last names). When the value of that field changes the writeable observable splits the name properly and re-updates the first and last name values. Additionally, we've changed our template to now use Virtual Bindings rather than depending on HTML elements for certain things. The virtual bindings can be found in the form of HTML comments that take this pattern: <!-- ko binding: observable -->Content...<!-- /ko -->. Take a look at the example below to see how this works:

 1 //Helper method to trim whitespace
 2 var trim = function(str) {
 3     return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
 4 };
 5 
 6 var viewModel = {}
 7 viewModel.first_name = ko.observable("");
 8 viewModel.last_name = ko.observable("");
 9 viewModel.full_name = ko.computed({
10     read: function(){
11         var first_name = viewModel.first_name() || "";
12         var last_name = viewModel.last_name() || "";
13         var full_name = first_name + " " + last_name;
14         
15         full_name = trim( full_name );
16         
17         return full_name;
18     },
19     
20     write: function( value ) {
21         value = value || "";
22         
23         //Split up the name
24         var pieces = value.split(" ", 2);
25         var first_name = trim( pieces[0] || "" );
26         var last_name = trim( pieces[1] || "" );
27            
28         //Update the observable values
29         viewModel.first_name( first_name );
30         viewModel.last_name( last_name );
31     }
32 });
33 
34 ko.applyBindings( viewModel );
Application Javascript
 1 <div>
 2     <label>Please enter your first name:</label>
 3     <input type="text" data-bind="value: first_name" />
 4 </div>
 5 <div>
 6     <label>Please enter your last name:</label>
 7     <input type="text" data-bind="value: last_name" />
 8 </div>
 9 - or -
10 <div>
11     <label>Enter your Full Name:</label>
12     <input type="text" data-bind="value: full_name" />
13 </div>
14 <hr />
15 <!-- ko if: full_name -->
16     Welcome, <!-- ko text: full_name --><!-- /ko -->!
17 <!-- /ko -->
Application HTML

Observable Arrays, Event Bindings, and Binding Contexts

Lastly, we'll discuss 3 other topics that should be understood before diving in to Falcon: Observable Arrays, Event Bindings, and Binding Contexts. In this example we'll let a user enter a first and last name of a person and add their name to a list of people. The user will also have the ability to remove specific names from the list of names.

Observable Arrays are just observables that hold an array of data. They are useful for updating lists and only re-rendering specific elements within the list upon, addition, change, removal, or movement of elements within the array. In this example, then 'names' observable array will hold a list of name objects. Each name object has an auto-incrementing index and a person's full name.

Also in this example we have two methods, 'addName' and 'removeName', that will be bound to buttons with the 'click' binding. The click binding is an example of an event binding. With the 'click' binding, the current template context will be passed in as the first argument. In other words, when using the 'foreach' binding on the 'names' observables array, a list of names is displayed to the user with a 'remove' button next to each. Each remove button is bound to the 'removeName' function on the viewModel through the 'click' binding. When a user clicks the remove button, that specific name object is passed into the 'removeName' function as the first argument.

Finally, if you look at how the 'removeName' function is bound to the 'click' binding in the html tab of the jsFiddle you'll see that we use click: $root.removeName. In Knockout, $root is called a Binding Context. Essentially, binding contexts are just variables that can be used in templates to help us navigate to different parts of the context hierarchy. In other words, the $root context always references the root viewModel that is bound with ko.applyBindings(). In our example we needed to use the $root binding from within the foreach binding because the current context would be that of each individual name object rather than the root view model. Other binding contexts include: $root, $parent, $parents, $data and more. To learn about them in-depth, checkout the Knockout Docs on Binding Contexts.

 1 //Helper method to trim whitespace
 2 var trim = function(str) {
 3     return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
 4 };
 5 
 6 var viewModel = {}
 7 viewModel.new_first_name = ko.observable();
 8 viewModel.new_last_name = ko.observable();
 9 viewModel.names = ko.observableArray([]);
10 
11 name_ids = 1;
12 viewModel.addName = function() {
13     // Make the new name from the given fields
14     var first_name = viewModel.new_first_name();
15     var last_name = viewModel.new_last_name();
16     var full_name = trim( first_name + " " + last_name );
17     
18     // Reset the new name fields
19     viewModel.new_first_name("");
20     viewModel.new_last_name("");
21     
22     // Add the name to the list of names
23     viewModel.names.push({
24         id: name_ids++, // Just an auto incrementing ID, for example sake
25         full_name: full_name
26     });
27 };
28 
29 viewModel.removeName = function( name_object ) {
30     viewModel.names.remove( name_object );
31 };
32 
33 
34 ko.applyBindings( viewModel );
Application Javascript
 1 <div>
 2     <label>Please enter a first name:</label>
 3     <input type="text" data-bind="value: new_first_name" />
 4 </div>
 5 <div>
 6     <label>Please enter a last name:</label>
 7     <input type="text" data-bind="value: new_last_name" />
 8 </div>
 9 <div>
10     <button data-bind="click: addName">Add</button>
11 </div>
12 <hr />
13 <ul data-bind="foreach: names">
14     <li>
15         <!-- ko text: id --><!-- /ko -->)
16         <!-- ko text: full_name --><!-- /ko -->
17         <button data-bind="click: $root.removeName">Remove</button>
18     </li>
19 </ul>
Application HTML