An Intro to Backbone.js: Part 3 – Binding a Collection to a View
So we’ve covered models, collections, controllers, and the basic case of tying a view to a model’s callbacks.
Now we’re going to cover tying a view to a collection. This gets a little hairy, so I’m going to keep it nice and simple to start.
Note: As with most things, there are large number of ways you could do this. If you feel that you have a better way, feel free to reply with it as a comment.
A Basic Collection View
Let’s say that we have an existing page that looks like this:
<header> OMG Donut Shop </header> <section> <header>Donut Selection</header> <ul class=donuts></ul> </section>
I’m going to define a DonutCollectionView that will, when given a collection of donuts, render an UpdatingDonutView for each donut.
This is a pattern I use fairly often. A View that is composed of other views. In this way you can easily reuse functionality between Views.
var DonutCollectionView = Backbone.View.extend({
initialize : function() {
var that = this;
this._donutViews = [];
this.collection.each(function(donut) {
that._donutViews.push(new UpdatingDonutView({
model : donut,
tagName : 'li'
}));
});
},
render : function() {
var that = this;
// Clear out this element.
$(this.el).empty();
// Render each sub-view and append it to the parent view's element.
_(this._donutViews).each(function(dv) {
$(that.el).append(dv.render().el);
});
}
});
Initialize
I didn’t go into much detail about this before. Backbone’s View, Model, and Collection all have their own constructors (which you don’t override*) that take care of initializing a number of functionalities, and the “initialize” method is run inside of their constructors.
*You could, but you’d better have a good reason.
Creating and Rendering
So let’s say I create a Backbone.Collection of donuts.
var donuts = new Donuts([
{"name" : "Boston Cream"},
{"name" : "Lemon-Filled"},
{"name" : "Rusty Iron Shavings"}
]);
I can then create a View with those Donuts.
var donutCollectionView = new DonutCollectionView({
collection : donuts,
el : $('ul.donuts')[0]
});
donutCollectionView.render();
This will render each of the donuts into the View and append them to the ul that I created earlier. Which will end up looking like this:
<header> OMG Donut Shop <header> <section> <header> Donut Selection <header> <ul class="donuts"> <li> Boston Cream </li> <li> Lemon-Filled </li> <li> Rusty Iron Shavings </li> </ul> </section>
So by virtue of using our UpdatingDonutView, the individual donuts will register changes and rerender.
But adding or removing an item from the collection won’t be reflected. Let’s alter the above DonutCollectionView and hook it into the Collection’s events.
As a reminder, the collection will trigger “add” events each time a model is added to it, and a “remove” event each time a model is removed.
So what would binding the view above to the collection’s events look like?
var DonutCollectionView = Backbone.View.extend({
initialize : function() {
// bind the functions 'add' and 'remove' to the view.
_(this).bindAll('add', 'remove');
// create an array of donut views to keep track of children
this._donutViews = [];
// add each donut to the view
this.collection.each(this.add);
// bind this view to the add and remove events of the collection!
this.collection.bind('add', this.add);
this.collection.bind('remove', this.remove);
},
add : function(donut) {
// We create an updating donut view for each donut that is added.
var dv = new UpdatingDonutView({
tagName : 'li',
model : donut
});
// And add it to the collection so that it's easy to reuse.
this._donutViews.push(dv);
// If the view has been rendered, then
// we immediately append the rendered donut.
if (this._rendered) {
$(this.el).append(dv.render().el);
}
},
remove : function(model) {
var viewToRemove = _(this._donutViews).select(function(cv) { return cv.model === model; })[0];
this._donutViews = _(this._donutViews).without(viewToRemove);
if (this._rendered) $(viewToRemove.el).remove();
},
render : function() {
// We keep track of the rendered state of the view
this._rendered = true;
$(this.el).empty();
// Render each Donut View and append them.
_(this._donutViews).each(function(dv) {
this.$('ul.donuts').append(dv.render().el);
});
return this;
}
});
The use of this DonutCollectionView is identical to the one above, but now it’s adding and removing properly.
Building a Generic UpdatingCollectionView
Alright, so that’s a lot of code to get an updating collection view for donuts. Maybe not the awesomest thing ever. But does it really need to be tightly coupled to Donuts or DonutViews? Let’s decouple this jerk and make a generic updating view that we can use like this.
var donutCollectionView = new UpdatingCollectionView({
collection : donuts,
childViewConstructor : UpdatingDonutView,
childViewTagName : 'li',
el : $('#donut_list')[0]
});
And here’s the implementation of that…
var UpdatingCollectionView = Backbone.View.extend({
initialize : function(options) {
_(this).bindAll('add', 'remove');
if (!options.childViewConstructor) throw "no child view constructor provided";
if (!options.childViewTagName) throw "no child view tag name provided";
this._childViewConstructor = options.childViewConstructor;
this._childViewTagName = options.childViewTagName;
this._childViews = [];
this.collection.each(this.add);
this.collection.bind('add', this.add);
this.collection.bind('remove', this.remove);
},
add : function(model) {
var childView = new this._childViewConstructor({
tagName : this._childViewTagName,
model : model
});
this._childViews.push(childView);
if (this._rendered) {
$(this.el).append(childView.render().el);
}
},
remove : function(model) {
var viewToRemove = _(this._childViews).select(function(cv) { return cv.model === model; })[0];
this._childViews = _(this._childViews).without(viewToRemove);
if (this._rendered) $(viewToRemove.el).remove();
},
render : function() {
var that = this;
this._rendered = true;
$(this.el).empty();
_(this._childViews).each(function(childView) {
$(that.el).append(childView.render().el);
});
return this;
}
});
Using the UpdatingCollectionView
So, now that we’ve made a generic updating collection view, let’s go through a potential use case of it!
Let’s say that we’re building a view for a DonutShop.
I’m starting to get nice and opinionated now and I’m going to bring in jQuery template
Here’s a template for a DonutShop
<script id="tmpl-donut_shop" type="template/jquery">
<header>{{name}}</header>
<section>
<header>Donuts offered:</header>
<ul class=donuts></ul>
</section>
</script>
And now I’ll declare a view that uses that template.
var DonutShopView = Backbone.View.extend({
templateId : 'donut_shop',
initialize : function() {
// Don't worry about this syntax too much.
// Basically all that this template bit does is grab a template from jQuery
// I'll have taken care of compiling the template somewhere earlier.
this.template = $.template(this.templateId);
this._donutCollectionView = new UpdatingCollectionView({
collection : this.model.donuts,
childViewConstructor : UpdatingDonutView,
childViewTagName : 'li'
});
},
render : function() {
$(this.el).empty();
// And here I use the template to render this object,
// then take the rendered template
// and append it to this view's element.
$.tmpl(this.template, this.model.toJSON()).appendTo(this.el);
this._donutCollectionView.el = this.$('.donuts');
this._donutCollectionView.render();
}
});
var donutShopView = new DonutShopView({ model : donutShop });
So now we’ve got a donut shop view, which contains an autoupdating collection view, which contains autoupdating model views.
Everything stays in sync.
And everyone is happy.
Next, I will be going over declaratively binding events to views, which is a pretty great thing.

Pingback: Quora
Pingback: Binding collections to views in Backbone.js
Pingback: Bookmarks for May 30th through June 6th | gregs