Tutorial: Getting Started with Knockback.js

In essence, Knockback.js bridges the dynamic DOM bindings of Knockout.js with the models, computeds, and routers of Backbone.js. Well, it also brings some other cool features like localization, default values, and nested view models.

Useful Resources

Before you start with Knockback.js, we recommend you first get acquainted with Backbone.js and Knockout.js by reviewing their websites (above) and this information:

For Knockback.js, also take a look at this comparison table between the libraries or this blog entry which provides a good introduction.

Minimal Study

Knockout.js

Knockout follows the Model_View_ViewModel (MVVM) design pattern meaning you specify a ViewModel (Controller) in addition to your data (Model) which encapsulates specific data and logic for your HTML/Template (View).

View (HTML):

<div id='ko_basic'>
  <p>First name: <input data-bind="value: first_name" /></p>
  <p>Last name: <input data-bind="value: last_name" /></p>
  <p>Hello, <span data-bind="text: full_name"> </span>!</p>
</div>

ViewModel and Bindings:

model = {first_name: "Planet", last_name: "Earth"}

ViewModel = (model) ->
  @first_name = ko.observable(model.first_name)
  @last_name = ko.observable(model.last_name)
  @full_name = ko.computed((->return "#{this.first_name()} #{this.last_name()}"), @)
  @
view_model = new ViewModel(model)

ko.applyBindings(view_model, $('#ko_basic')[0])
// Generated by CoffeeScript 1.3.3
var ViewModel, model, view_model;

model = {
  first_name: "Planet",
  last_name: "Earth"
};

ViewModel = function(model) {
  this.first_name = ko.observable(model.first_name);
  this.last_name = ko.observable(model.last_name);
  this.full_name = ko.computed((function() {
    return "" + (this.first_name()) + " " + (this.last_name());
  }), this);
  return this;
};

view_model = new ViewModel(model);

ko.applyBindings(view_model, $('#ko_basic')[0]);

Live Result

First name:

Last name:

Hello, !

Note: ViewModels need to be unique instances so Knockout uses a simple function with new to create an instance. Here are some equivalents:
# Form 1: Simple function-based class
ViewModel = (model) ->
  @first_name = ko.observable(model.first_name)
  @ # note: need to return this/@ since CoffeeScript returns the last argument automatically (which is not what we want)
  
view_model = new ViewModel({first_name: "Hello"})

# Form 2: CoffeeScript class (allows for inheritance)
class ViewModel
  constructor: (mode) ->
    @first_name = ko.observable(model.first_name)
    
view_model = new ViewModel({first_name: "Hello"})
// Simple function-based class
var ViewModel = function(model) {
  this.first_name = ko.observable(model.first_name);
};

var view_model = new ViewModel({first_name: "Hello"});

Backbone.js

Backbone is an amazing Model_View_Controller (MVC)-inspired library that handles data (Model/Collection) synchronization with a RESTful server for Object_Relational_Mapping (ORM), page routing, etc. Unfortunately, their Views becomecomplex to maintain when dynamic Model and View synchronization is required (which is why Knockback exists).

These are the basic Model components from Backbone used by Knockback:
// Model
earth = new Backbone.Model({first_name: 'Planet', last_name: 'Earth'})
mars = new Backbone.Model({first_name: 'Planet', last_name: 'Mars'})

// Collection
planets = new Backbone.Collection([earth, mars])
// Model
var earth = new Backbone.Model({first_name: 'Planet', last_name: 'Earth'});
var mars = new Backbone.Model({first_name: 'Planet', last_name: 'Mars'});

// Collection
var planets = new Backbone.Collection([earth, mars]);
This is a Backbone router that can be used by your app:

Router:

class PageRouter extends Backbone.Router
  routes:
    "earth":    "switchToEarth"     // #earth
    "mars":     "switchToMars"      // #mars
    
  switchToEarth: -> # do something
  switchToMars: -> # do something
  
# instantiate the router and start listening for URL changes
page_router = new PageRouter()
Backbone.history.start()
var PageRouter = Backbone.Router.extend({
  routes: {
    "earth":    "switchToEarth",     // #earth
    "mars":     "switchToMars"      // #mars
  },
  
  switchToEarth: function() { /* do something */ },
  switchToMars: function() { /* do something */ }
});

// instantiate the router and start listening for URL changes
var page_router = new PageRouter()
Backbone.history.start()

Knockback.js

You can use Knockback to bind Backbone Models/Collections to your HTML/templates (View) using Knockout. This allows server or application changes to Models/Collections to be propagated automatically to your views using `Backbone.sync`

Using with a Backbone.Model

Creating a ko.observable() for each model attribute by hand:

View (HTML):

<div id='kb_observable'>
  <p>First name: <input data-bind="value: first_name" /></p>
  <p>Last name: <input data-bind="value: last_name" /></p>
  <p>Hello, <span data-bind="text: full_name"> </span>!</p>
</div>

ViewModel and Bindings:

model = new Backbone.Model({first_name: "Planet", last_name: "Earth"})

ViewModel = (model) ->
  @first_name = kb.observable(model, 'first_name')
  @last_name = kb.observable(model, 'first_name')
  @full_name = ko.computed((->return "#{this.first_name()} #{this.last_name()}"), @)
  @
view_model = new ViewModel(model)

ko.applyBindings(view_model, $('#kb_observable')[0])
// Generated by CoffeeScript 1.3.3
var ViewModel, model, view_model;

model = new Backbone.Model({
  first_name: "Planet",
  last_name: "Earth"
});

ViewModel = function(model) {
  this.first_name = kb.observable(model, 'first_name');
  this.last_name = kb.observable(model, 'first_name');
  this.full_name = ko.computed((function() {
    return "" + (this.first_name()) + " " + (this.last_name());
  }), this);
  return this;
};

view_model = new ViewModel(model);

ko.applyBindings(view_model, $('#kb_observable')[0]);

Live Result

First name:

Last name:

Hello, !

Creating a ko.observable() automatically for each model attribute:

View (HTML):

<div id='kb_view_model_computed'>
  <p>First name: <input data-bind="value: first_name" /></p>
  <p>Last name: <input data-bind="value: last_name" /></p>
  <p>Hello, <span data-bind="text: full_name"> </span>!</p>
</div>

Models, Collection, ViewModel, and Bindings:

model = new Backbone.Model({first_name: "Planet", last_name: "Earth"})

view_model = kb.viewModel(model)
view_model.full_name = ko.computed((->return "#{@first_name()} #{@last_name()}"), view_model)

ko.applyBindings(view_model, $('#kb_view_model_computed')[0])
// Generated by CoffeeScript 1.3.3
var model, view_model;

model = new Backbone.Model({
  first_name: "Planet",
  last_name: "Earth"
});

view_model = kb.viewModel(model);

view_model.full_name = ko.computed((function() {
  return "" + (this.first_name()) + " " + (this.last_name());
}), view_model);

ko.applyBindings(view_model, $('#kb_view_model_computed')[0]);

Live Result

First name:

Last name:

Hello, !

Using with a Backbone.Collection

Create a kb.ViewModel automatically for each model in a collection:

View (HTML):

<div id='kb_collection' data-bind="foreach: planets">
  <p>First name: <input data-bind="value: first_name" /></p>
  <p>Last name: <input data-bind="value: last_name" /></p>
  <p>Hello, <span data-bind="text: ko.computed(function(){ return $data.first_name() + ' ' + $data.last_name(); })"> </span>!</p>
</div>

Models, Collection, ViewModel, and Bindings:

earth = new Backbone.Model({first_name: 'Planet', last_name: 'Earth'})
mars = new Backbone.Model({first_name: 'Planet', last_name: 'Mars'})
the_moon = new Backbone.Model({first_name: 'The', last_name: 'Moon'})

planets = new Backbone.Collection([earth, the_moon, mars])

view_model =
  planets: kb.collectionObservable(planets, {view_model: kb.ViewModel})

ko.applyBindings(view_model, $('#kb_collection')[0])
// Generated by CoffeeScript 1.3.3
var earth, mars, planets, the_moon, view_model;

earth = new Backbone.Model({
  first_name: 'Planet',
  last_name: 'Earth'
});

mars = new Backbone.Model({
  first_name: 'Planet',
  last_name: 'Mars'
});

the_moon = new Backbone.Model({
  first_name: 'The',
  last_name: 'Moon'
});

planets = new Backbone.Collection([earth, the_moon, mars]);

view_model = {
  planets: kb.collectionObservable(planets, {
    view_model: kb.ViewModel
  })
};

ko.applyBindings(view_model, $('#kb_collection')[0]);

Live Result

First name:

Last name:

Hello, !

Inheritance

Some Knockback classes are meant to be inherited. Specifically:

Re-wrtting the above kb.viewModel example:

View (HTML):

<div id='kb_view_model_inheritance'>
  <p>First name: <input data-bind="value: first_name" /></p>
  <p>Last name: <input data-bind="value: last_name" /></p>
  <p>Hello, <span data-bind="text: full_name"> </span>!</p>
</div>

ViewModel and Bindings:

model = new Backbone.Model({first_name: "Planet", last_name: "Earth"})

class ViewModel extends kb.ViewModel
  constructor: (model) ->
    super
    @full_name = ko.computed((->return "#{@first_name()} #{@last_name()}"), @)
view_model = new ViewModel(model)

ko.applyBindings(view_model, $('#kb_view_model_inheritance')[0])
var model = new Backbone.Model({
  first_name: "Planet",
  last_name: "Earth"
});

var ViewModel = kb.ViewModel.extend({
  constructor: function(model) {
    ViewModel.__super__.constructor.apply(this, arguments);
    this.full_name = ko.computed((function() {
      return "" + (this.first_name()) + " " + (this.last_name());
    }), this);
  }
});

var view_model = new ViewModel(model);

ko.applyBindings(view_model, $('#kb_view_model_inheritance')[0]);

Live Result

First name:

Last name:

Hello, !

Classes vs Factory Functions

You may have noticed that there is both kb.viewModel(model, options) and kb.ViewModel. The reason is that to reduce the need of using `new` and to be more familiar with Knockout, there are factory methods that create the underlying classes.

A factory function uses new to create a new instance:

Knockback.viewModel = (model, options) -> return new Knockback.ViewModel(model, options)
Knockback.viewModel = function(model, options) { return new Knockback.ViewModel(model, options); };
Be careful to differentiate the factory from the class when using kb.utils.observableInstanceOf(observable, class)