Defining viewmodels
Viewmodels are the objects whose properties your view binds with; they form the binding context. It is the representation of your data and operations for your view (we will cover them in detail in the Control flow bindings section later in this chapter). Like regular objects in JavaScript, there are many ways to actually create them, but Knockout introduces some specific challenges.
The this and self keywords
In JavaScript, this
has a special meaning; it refers to the object calling the function. Functions called from an object get that object set to this
. However, for functions that are anonymously called by code, that is merely the inside of an object, the behavior is different. Consider the following viewmodel:
function Invoice() { this.subtotal = ko.observable(); this.total = ko.computed(function() { return this.subtotal() * 1.08; //Tax Rate }); }
The function inside the computed observable is not a property of the Invoice
object. As it runs in a different context, its value for this will be the window object, not the Invoice
object. It will not be able to find the subtotal
property. There are two ways to handle this.
The first is by using the second parameter of the ko.computed
function to bind the function to this
:
function Invoice() { this.subtotal = ko.observable(); this.total = ko.computed(function() { return this.subtotal() * 1.08; //Tax Rate }, this); }
This gives the computed observable a reference to the Invoice
that originally defined it, which allows the computed observable to call the supplied function in the correct context.
The second way to ensure the computed observable can reference the subtotal
, is to capture the value of this
in a closure. You can then use the closure to safely refer to the properties of the parent viewmodel. There are several conventional names for such a closure: that
, _this
, or self
.
I prefer to use self
as it is visually distinct from this
while still carrying a similar meaning, but it's up to you:
function Invoice() { var self = this; self.subtotal = ko.observable(); self.total = ko.computed(function() { return self.subtotal() * 1.08; //Tax Rate }); }
I find the second method easier to remember. If you always use self
to refer to the model, it will always work. If you have another anonymous function inside the computed, you will have to remember to bind that function as well; self
continues to work as a closure no matter how many levels deep you nest. The self
variable works as a closure inside any function defined in your viewmodel, including subscriptions. It's also easier to spot when self
isn't being used, which is very helpful while debugging your code.
Problems with prototypes
If you are working with viewmodels that will be inherited by other viewmodels, you might think that putting all the base observable properties on the prototype is the way to go. In vanilla JavaScript, if you are inheriting an object, try to change the value of a property stored on the prototype; the property would be added to the inheriting object leaving the prototype intact. When using observables in Knockout though, this isn't the case. The observables are functions, and their values are set by calling them with a single parameter, not by assigning new values to them. Because prototypical inheritance would result in multiple objects referring to a single observable; observables cannot be safely placed on viewmodel prototypes. Nonobservable functions can still be safely included in prototypes. For example, consider the following objects:
var protoVm = { name: ko.observable('New User') }; var base1 = Object.create(protoVm); var base2 = Object.create(protoVm); base2.name("Base2");
The last line will cause the name of both objects to be updated, as it is referring to the same function. This example can be seen in the cp1-prototype
branch, which includes two input elements bound to the name of each viewmodel. As they are really the same observable, changing one will affect the other.
Serializing viewmodels
When you are ready to send your viewmodels to the server, or really do anything that requires you to work with their values instead of observables, Knockout provides two very handy utility methods:
ko.toJS
: This function takes an object and does a deep copy, unwrapping all observables, into a new JavaScript object whose properties are normal (nonobservable) JavaScript values. This function is perfect to get copies of viewmodels.ko.toJSON
: This function uses the output fromko.toJS
withJSON.stringify
to produce a JSON string of the supplied object. This function accepts the same parameters asJSON.stringify
.