Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
MASTERING KNOCKOUTJS

You're reading from   MASTERING KNOCKOUTJS Use and extend Knockout to deliver feature-rich, modern web applications

Arrow left icon
Product type Paperback
Published in Nov 2014
Publisher
ISBN-13 9781783981007
Length 270 pages
Edition 1st Edition
Arrow right icon
Author (1):
Arrow left icon
Timothy Moran Timothy Moran
Author Profile Icon Timothy Moran
Timothy Moran
Arrow right icon
View More author details
Toc

Table of Contents (11) Chapters Close

Preface 1. Knockout Essentials 2. Extending Knockout with Custom Binding Handlers FREE CHAPTER 3. Extending Knockout with Preprocessors and Providers 4. Application Development with Components and Modules 5. Durandal – the Knockout Framework 6. Advanced Durandal 7. Best Practices 8. Plugins and Other Knockout Libraries 9. Under the Hood Index

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 from ko.toJS with JSON.stringify to produce a JSON string of the supplied object. This function accepts the same parameters as JSON.stringify.
lock icon The rest of the chapter is locked
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at R$50/month. Cancel anytime