I had a lot of work and things to do, so I couldn’t write.
The last year my boss asked me to look up all the libraries like Backbone.js and Knockout. The idea was to check all the options and take the more convenient.
To accomplish that I began to read and look a lot of information. Besides, I took the opportunity to try each one and feel them. For that, I made an application in Backbone.js and then observed how it would be using Knockout.
I will show you the code and then give my opinion. That way you will have a better decision.
Backbone.js
It’s really powerful and used by a lot of people, but it have an hard learning curve. You have a model and a bunch of things to define. If you follow all the conventions, the code will be easy to mantain. Here you can see more information and details.
It is ideal for REST web servicies.
Creating this application took me one noon and part of the morning.
The html:
<div>
<fieldset>
<legend>Person List - Using Backbone</legend>
<div id="person-container">
<input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
<table id="person-list">
<tr>
<th>
Name
</th>
<th>
Description
</th>
<th>
</th>
</tr>
</table>
</div>
<script id='person-template' type='text/template'>
<td><%=ID%></td> <td><%=FirstName%></td> <td><%=LastName%></td> <td><%=Age%></td>
<td><input type="button" value="Edit" class="person-edit" /> | <input type="button" value="Details" class="person-detail" /> | <input type="button" value="Delete" class="person-delete" /></td>
</script>
</fieldset>
</div>
Javascript:
$(function () {
// Person Model
var Person = Backbone.Model.extend({
urlRoot: '/Person/List/',
initialize: function () {
console.log('Person initialize');
},
defaults: {
ID: 0,
FirstName: 'Unknown',
LastName: 'Unknown',
Age : 0
}
});
// Person Collection
var PersonCollection = Backbone.Collection.extend({
model: Person,
url: '/Person/List/'
});
// Person View - el returns the template enclosed within a tr
var PersonView = Backbone.View.extend({
template: _.template($('#person-template').html()),
tagName: "tr",
initialize: function () {
console.log('PersonView initialize');
this.model.bind('change', this.render, this);
this.model.bind('remove', this.unrender, this);
},
render: function () {
console.log('PersonView render');
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
unrender: function () {
console.log('PersonView unrender');
$(this.el).remove();
return this;
},
events: {
"click .person-edit": 'EditPerson',
"click .person-delete": 'DeletePerson'
},
EditPerson: function () {
console.log('PersonView EditPerson');
this.model.set({ FirstName: 'Unknown' });
var self = this;
this.model.save(this.model, { success: function () {
$("input:button", $(self.el)).button();
}});
},
DeletePerson: function () {
console.log('PersonView DeletePerson');
this.model.destroy();
}
});
// Actual App view
var AppView = Backbone.View.extend({
initialize: function () {
console.log('AppView initialize');
this.collection.bind('add', this.AppendPerson, this);
},
el: '#person-container',
counter: 3,
events: {
"click #btn-create-new-person": "AddNewPerson"
},
AddNewPerson: function () {
console.log('AppView AddNewPerson');
this.counter++;
var newPerson = new Person({ ID: this.counter, FirstName: 'Unknown ' + this.counter, LastName: 'Damn ' + this.counter, Age: this.counter });
this.collection.add(newPerson);
newPerson.save(newPerson, { success: function () {
$("input:button", "#person-list").button();
}});
},
AppendPerson: function (person) {
console.log('AppView AppendPerson');
var personView = new PersonView({ model: person });
$(this.el).find('table').append(personView.render().el);
},
render: function () {
console.log('AppView render');
if (this.collection.length > 0) {
this.collection.each(this.AppendPerson, this);
}
$("input:button", "#person-list").button();
}
});
var persons = new PersonCollection();
var view = new AppView({ collection: persons });
persons.fetch({ success: function () {
console.log('Fetch called');
view.render();
}});
});
In the server (my case asp.net MVC with C#):
public class PersonController : Controller
{
List<Person> Persons = new List<Person> {
new Person (1, "Federico", "Ponte" , 23),
new Person (2, "Alejandro", "Drago" , 23),
};
// Get Person
[AcceptVerbs(HttpVerbs.Get)]
public JsonResult List(int? ID)
{
if (ID.HasValue)
{
Person PersonResult = Persons.Find(p => p.ID == ID);
return Json(PersonResult, JsonRequestBehavior.AllowGet);
}
return Json(Persons, JsonRequestBehavior.AllowGet);
}
// Update Person
[AcceptVerbs(HttpVerbs.Put)]
public JsonResult List(int ID, Person UpdatedPerson)
{
Person PersonResult = Persons.Find(p => p.ID == ID);
PersonResult.FirstName = UpdatedPerson.FirstName;
PersonResult.LastName = UpdatedPerson.LastName;
PersonResult.Age = UpdatedPerson.Age;
return Json(PersonResult, JsonRequestBehavior.DenyGet);
}
//Add Person
[AcceptVerbs(HttpVerbs.Post)]
public JsonResult List(Person CreatePerson)
{
Persons.Add(CreatePerson);
return Json(CreatePerson, JsonRequestBehavior.DenyGet);
}
//Delete Person
[AcceptVerbs(HttpVerbs.Delete)]
public JsonResult List(int ID)
{
Person PersonResult = Persons.Find(p => p.ID == ID);
Persons.Remove(PersonResult);
return Json(PersonResult, JsonRequestBehavior.DenyGet);
}
}
public class Person
{
public Person(int _ID, string _FirstName, string _LastName, int _Age)
{
ID = _ID;
FirstName = _FirstName;
LastName = _LastName;
Age = _Age;
}
public int ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
As you can see it is laborious, but it have advantages: highly configurable, it works on cellphones, you use web services. It is easy to scale with it.
It’s pretty tricky at the begging, but once you understand it, is simple.
Knockout
It use the pattern Model-View-View Model. It is quite easy to learn and pretty powerful. It comes with a complete template system and a lot of functions. You need to create a model and modify it directly in order to update the views. It have a pretty good documentation and it’s simple. To know more enter here.
One model would be:
function PersonModel () {
var self = this;
self.ID = ko.observable(0);
self.FirstName = ko.observable('Unknown');
self.LastName = ko.observable('Unknown');
self.Age = ko.observable(0);
}
function PersonCollectionModel () {
var self = this;
self.Persons = ko.observableArray()
}
$(function (){
ko.applyBindings(new PersonCollectionModel());
});
The functionality is pretty boring and it is not structurated. An example would be:
function PersonModel () {
var self = this;
self.ID = ko.observable(0);
self.FirstName = ko.observable('Unknown');
self.LastName = ko.observable('Unknown');
self.Age = ko.observable(0);
self.changeAndAlert = function (){
self.FirstName('Hola');
self.LastName('Chao');
alert('Me cambie a ' + self.FirstName() + ' ' + self.LastName());
}
}
<div>
<fieldset>
<legend>Person List - Using Knockout</legend>
<div id="person-container">
<input type="button" value= "Create New" class="Add" id="btn-create-new-person" />
<table id="person-list">
<tr>
<th>
Name
</th>
<th>
Description
</th>
<th>
</th>
</tr>
<!-- ko foreach : persons -->
<tr>
<td data-bind="text : FirstName"></td>
<td data-bind="text : LastName"></td>
<td><input type="button" data-bind="click : changeAndAlert" value="ChangeAndAlert" /></td>
</tr>
<!-- /ko -->
</table>
</div>
</fieldset>
</div>
The rest of the functionality in the case of Backbone is quite easy to do. With a good jQuery knowledge or another library, it just take some Ajax calls and modify the model.
Comparision
Backbone is quite tricky and hard to learn. It gives maintainability and scalability.
I have used more Knockout and it is pretty simple.
At this time I am writting this i don’t have any preferences as before. Both can do the same, the decision is on what you need.
Others
This library is sponsored by Google.
Angular JS