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).
<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>
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]);
First name:
Last name:
Hello, !
# 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 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).
// 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]);
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()
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`
Creating a ko.observable() for each model attribute by hand:
<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>
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]);
First name:
Last name:
Hello, !
Creating a ko.observable() automatically for each model attribute:
<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>
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]);
First name:
Last name:
Hello, !
Create a kb.ViewModel automatically for each model in a collection:
<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>
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]);
First name:
Last name:
Hello, !
Some Knockback classes are meant to be inherited. Specifically:
Re-wrtting the above kb.viewModel example:<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>
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]);
First name:
Last name:
Hello, !
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); };