Backbone View-Model Binding

This article briefly discusses how the Backbone.View class is normally used and then introduces a new class that helps makes it easy to synchronize your models and views.

The Backbone.View class helps you do a couple of important things that I’ll discuss in very brief detail:  (I assume that you are already familiar with Backbone.Views)

  • Create browser DOM elements
  • Listen to DOM element events
  • Listen to model events

Create browser DOM elements:

Backbone.View objects have a default view.el field that is a DOM element.  By default it’s a <div> but you can override it to be any type of element you want.  Normally, the View.render( ) function’s main job is to fill the view.el with DOM elements that make up the view.

The view also has a view.$el function which is just shorthand for $(view.el).  In the example below, the render function just creates a input DOM element and appends it to the view’s el property – which is just a div.  Nothing will be rendered in the browser at this point.  The calling function would probably add the views.el property somewhere in the browser’s DOM to have it actually show up for the user.

  render: function() {
    this.$el.html('Enter your name: <input type="text"/>);
    return this;
  }

In almost all situations, you won’t inline your html inside of your view classes.  Instead, you’ll have separate html files that are your html templates.  Html templates are then passed to the underscore.js template function which can be passed parameters that are interpreted and html with injected variable values is then returned to be rendered as DOM elements.

Listen to DOM element events:

Backbone.View class lets you define an events hash.  The keys of the events hash are jQuery events followed by jQuery selectors.  The values of the events hash are function call backs that are invoked when the event is raised.

The events are defined as jQuery delegates.  This is nice because delegates are a fairly efficient eventing mechanism that are also dynamic.  When your view.el contents are swapped out, the jQuery delegates don’t need to be refreshed.

Listen to model events:

The Backbone.View class typically listens for when it’s model changes and then calls the view.render( ) function as shown below.

SomeView = Backbone.View.extend({    initialize: function(){
    this.model.on('change', this.render, this);
  }, 

  render: function() {
    this.$el.html(this.template(this.model.toJSON()));
    return this;
  }
});

 

Problems with the typical backbone View code:

For smaller applications, the approach described above works well.  Especially in these type of situations:

  • The application’s models don’t change very often
  • The application’s pages are frequently refreshed from scratch from the server
  • Model’s aren’t shared between different views

As applications become more complex though, calling render( ) too many times can be wasteful.  The old DOM elements are just thrown away.  Also, you usually have many read-only or editable fields on a page and plumbing them up manually can be a pain in the butt.  Life gets worse when you have special formatting or conversion between how the model stores data and how the view renders the data.

Moving data from the view to the model can be performed in bulk with something like the jQuery serializeArray function but this won’t be ideal when you want other visible views to update immediately instead of on some commit.  serializeArray doesn’t any mechanism to allow you to convert view formats into model formats either.

The ModelBinder:

The ModelBinder class is designed to make it easier to synchronize your views and models together.  It eliminates a few of the problems described above.  When a view’s model changes, the entire view isn’t re-rendered.  Instead, individual elements are refreshed to have the current model values.  When a view’s element changes, the corresponding model attribute is updated.

The  ModelBinder class has the following public functions:

// no arguments passed to the constructor
constructor();

// model is required, it is the backbone Model your binding to
// rootEl is required, is the root html element containing the elements you want to bind to
// bindings is optional, it's discussed a bit later
bind(model, rootEl, bindings);

// unbinds the Model with the elements found under rootEl - defined when calling bind()
unbind();

Typically, ModelBinders will be used like this:

SomeView = Backbone.View.extend({ 
 initialize: function(){ this._modelBinder = new Backbone.ModelBinder(); }, 
 render: function(){ this.$el.html(this.template()); this._modelBinder.bind(this.model, this.el); }, close: function(){ this._modelBinder.unbind(); } }); 

 

Typically, a view will hold onto model binder instances because the view should call unbind( ) when the view closes.  Otherwise you’ll end up with zombie views that don’t die – you would have the same issue if you just relied on traditional model change events.    The view’s render( ) function will only need to be called once.  The template( ) function will not need to be passed any arguments because the ModelBinder will inject the proper model attributes inside the corresponding DOM elements under the view.el.

In the example above, the bind( ) function’s 3rd argument is undefined.  When the bindings argument is empty, the ModelBinder will locate all of the child elements (recursively) underneath the rootEl that have a “name” attribute defined.  For each of the found elements, the element’s name attribute will be bound to the model’s attribute.

So, a rootEl’s child element :  <input type=”text” name=”phone”/>   will be bound to model.phone.

The ModelBinder bindings parameter:

The 3rd parameter to the bind( ) function should follow this syntax:

// Key: a model attribute         Value: a jQuery selector
{  phone:                         '[name=phone]' }

In the example above, the model.phone is bi- bidirectionally bound to DOM elements with a name attribute that equals “phone”.  The jQuery selector can be any valid jQuery selector statement.  The ModelBinder only expects that a single DOM element is returned – multiple elements is OK.

By default, the DOM element’s text or html is bound to the model (depending on the DOM type) but you can also bind to arbitrary DOM element attributes like the example below by using the “elAttribute” option:

// Key: a model attribute         Value: an element binding definition
{  isAddressEnabled:              {selector: '[name=address]',  elAttribute: 'enabled'} }

 

You can also format/convert bound values with a “converter” option as shown below:

// Key: a model attribute         Value: an element binding definition
{  phone:                         {selector: '[name=phone]', converter: phoneConverter }  }

The phoneConverter in the example above is a function that the ModelBinder will invoke when it copies values from the model to the view or the view to the model.  Converters are passed 4 values as shown below.

phoneConverter: function(direction, value, attributeName, model){ ... }

The direction is either ModelToView or ViewToModel.  The converter can exist on your model instances or in some generic utility function because it’s passed the model as a parameter.  For read-only elements, you can ignore the direction because it’s always ModelToView.

You can use the “elAttribute” option along with the “converter” option when creating your bindings.  The “converter” is invoked before the “elAttribute” value is set.

The elements can also be defined as an array as shown below:

// Key: a model attribute         Value: an array of element binding definition
{  phone:                         [{selector: '[name=phone]'}  {selector: '#phone'}] }

In the example above, we bound model.phone to DOM elements that  have a name of phone and DOM elements that have an id of phone.

Since you can explicitly define your binding scope, you can do things like have nested backbone views.  Even if the views have DOM elements with the same name.  You can also use the ModelBinder as a partial view-model binding solution but still have custom logic for really complicated situations that the ModelBinder can’t handle.

If this sounds useful to you, you can try it out here:

https://github.com/theironcook/Backbone.ModelBinder

bartwood
I've had fun developing software in many platforms and languages for the past 12 years.

12 Comments on "Backbone View-Model Binding"

  1. Alex says:

    Hi! Great work, thanks!
    But there is one thing I can not understand. How should I use Model.Binder to add/remove attribute “checked” from binded input with type=”checkbox”.

  2. Bart Wood says:

    Thanks Alex,

    The ModelBinder takes care of the “checked” attribute for radio buttons automatically. You bind to radio button groups just like any other model binding.

    This example shows how the radio button group “eyeColor” is bound in this example:

    https://github.com/theironcook/Backbone.ModelBinder/blob/master/examples/Example2.html

    You can bind Model attributes to any DOM element attribute with the “elAttribute” option in your bindings but it’s automatically handled by the ModelBinder for the radio button “checked” attribute.

  3. Agelos Pikoulas says:

    Hi, great introduction Bart, thanks.

    Do you think Model Binder can be used to 2 way manipulation of jquery ui widgets ?

    Being a fan of knockout, I am investigating ways of achieving something similar to what wijmo has done with their knockout bindings, but using backbone models and application structure.

    So far I have found knockback to the rescue, but I am tempted to look for a more ‘native’ to backbone solution.

  4. Bart says:

    Hi Agelos,

    The model binder just relies on jquery functions for getting/setting values from browser elements. My project uses some of the jquery ui widgets with the model binder. You should be able too.

  5. jsmarkus says:

    You have reinvented KnockoutJS -)

    • isochronous says:

      No he hasn’t, because knockoutJS relies on declarative model binding via markup attributes, while the model binding in this library can be entirely encapsulated within the view.js file. While the inputs still have to have unique identifiers, it doesn’t rely on extra data- attributes to work.

  6. Mohit Jain says:

    Pretty nice article. learned some writing tips from you. I just started writing a series for backbone js learning and covered the same topic ie listening to dom events in backbone js. It wil great to have your feedback on it..
    http://www.codebeerstartups.com/2012/12/12-listening-to-dom-events-in-backbone-js-learning-backbone-js/

  7. アバクロンビー&フィッチ

  8. Xavier says:

    must have addon !!

  9. Rory says:

    Often if you regret on your part and finding qualified contractors by the contractors and has the
    necessary investigations. These Contractors Jersey City
    Medical Center, where you finally select. If you’re still unsure,
    ask if and when someone comes over to the school
    bus work completed?

    my blog post … web page (Rory)

Got something to say? Go for it!