Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

A new Class to Bind Backbone Collections to Views: Javascript Weekly May 18th

Rovanion edited this page · 10 revisions


The Problem:

In most backbone apps you'll need to render a backbone collection's models inside a view. Each of the collection's models will probably rendered inside a <tr>, <div> etc. The view that created each of the nested <tr> or <div> elements will need to listen to the collection's add/remove/reset events and then add or remove the nested <tr>, <div> elements that correspond to the added or removed model. A common situation is when a collection is rendered in a table and each table row corresponds to a model.

I've noticed the same pattern in many places in code I've worked on so I decided to create a reusable class to handle this type of situation. I've created a new class Backbone.CollectionBinder to help a bind a collection to a view.

Some key features of the CollectionBinder:

  • Automatically create/remove views when models are added/removed to a collection
  • Auto created views can be backbone views or just DOM elements
  • You can leverage the existing ModelBinder class to bind your nested models and nested views


The CollectionBinder has the following public functions:

// elManagerFactory - an object that creates elManagers for the models in the collection. 
//                    An elManager creates/removes DOM elements for the corresponding model it's associated with.
constructor(elManagerFactory);

// collection - a backbone collection that contains all of the models to render
// parentEl - the DOM element where all of the auto created nested views will be added/removed
bind(collection, parentEl);

// unbinds collection from the CollectionBinder
// if nested views were bound via the ModelBinder they are unbound
unbind();

// Returns the elManager that manages the el passed to the function.  
// The el might be a root el or a nested DOM element.
getManagerForEl(el);

// Returns the elManager that manages the model passed to the function
getManagerForModel(model)




Example of the CollectionBinder creating table rows

In the example below, the collection is rendered in an html table. Each row in the table corresponds to a model in the collection. The backbone view renders the table and the table's header and then it relies on the CollectionBinder to render the table's rows that correspond to the collection's models.

// View's html template
<table>
  <thead> <tr> <th>First Name</th> <th>Last Name</th> </tr> </thead>
  <tbody> </tbody>
</table>

// Table row's html template - will be stored in the javascript variable rowHtml
<tr> <td data-name="firstName"></td> <td data-name="lastName"></td> </tr>
// The backbone view that holds the collection
initialize: function(){
  var elManagerFactory = new Backbone.CollectionBinder.ElManagerFactory(rowHtml, "data-name");
  this._collectionBinder = new Backbone.CollectionBinder(elManagerFactory);
},

render: function(){
  this.$el.html(this.template());
  this._collectionBinder.bind(this.collection, this.$('tbody'));
  return this;
},

close: function(){
  this._collectionBinder.unbind();
}


In the example above, the bind() function will create a table row for each of the models found in the collection. The table rows were created using the rowHtml variable which is just normal html. If models are added/removed from the collection, corresponding table rows would be added/removed from the table. The rows are synchronized with the models as well using the existing ModelBinder.

In the example, when the view closes the unbind() function is called - if you don't call unbind() you might end up with zombie listeners / objects that don't die.

The bind() function uses an ElManagerFactory to create/remove the table rows via ElManagers that the factory creates. This is explained in the next section.

Here's a working jsFiddle written from the above example: http://jsfiddle.net/4r8ET/2/



How bind() works

When collectionBinder.bind(collection, parentEl) is called, the collectionBinder will create an elManager for each model in the collection. It will use the elManagerFactory to create these elManagers. The collectionBinder listens for when models are added/removed and will create/remove elManagers. collectionBinder will use the elManagerFactory to create/remove elManagers.

An elManager is created for each model in the collection. The elManager is responsible for creating/removing DOM elements that correspond to how the model should be visualized in a web page.

When an elManager is created, it's createEl() function is invoked. The createEl() function should create whatever DOM elements that are needed to render the model and add those to the DOM.

There are 2 default ElManagerFactory/ElManager classes that I've created that should be able to handle most of the situations you face but if you need to create your own elManager it's possible. The basic contract for an ElManagerFactory and ElManager are shown below.

// Contract for an ElManagerFactory
makeElManager(model);

// Contract for an ElManager
// Creates a DOM element that corresponds to the model for this manager. 
createEl();

// Remove the element that was previously created
removeEl();

// Return true or false depending on if findEl is under the elements created by createEl()
isElContained(findEl);

getModel();

getEl();




There are 2 default ElManagerFactory/ElManager classes that I've created:

  1. ElManagerFactory - creates raw DOM elements and bypasses the need for backbone views (shown in a previous example)
  2. ViewManagerFactory - creates backbone views for your collection models.


ElManagerFactory

In a previous example, I showed how to use the ElManagerFactory. I prefer this class because I don't need to define full blown backbone views for nested DOM elements.

The ElManagerFactory constructor takes the following parameters:

  1. parentEl - where the nested child elements will be appended/removed
  2. elHtml - an html string that defines the DOM elements to create per model in the collection. Should have a single root el
  3. bindings (optional) - Either a ModelBinder hash or a string that is the DOM element attribute name to bind to ("id", "name", etc.)

The ElManagerFactory has the following events: These events can be useful if you need to recalculate sizes etc. when nested view elements are created/removed.

  1. elCreated(model, el)
  2. elRemoved(model, el)

here is a more complete example of how to use the ElManagerFactory. The example shows the capabilities but not best practices.




ViewManagerFactory

The ViewManagerFactory helps to manage nested backbone views. It's constructor takes the following parameters:

  1. parentEl: the element that the dynamically created views should be appended to
  2. viewCreator: a function to create a backbone view instance that will be created for every model in your collection DefaultViewManagerFactory = function(parentEl, viewClass, viewCollection)

The ViewManagerFactory has the following events: These events can be useful if you need to recalculate sizes etc. when nested view elements are created/removed.

  1. elCreated(model, view)
  2. elRemoved(model, view)

here is a more complete example of how to use the ViewManagerFactory.

Something went wrong with that request. Please try again.