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); };