Husband, frontend @blimp, MMA addict, gamer, occasional guitarist, mystery enthusiast and conspiracy theorist.

Rethinking Backbone.js View Rendering

I’ve used Backbone for over a year now, and every once in a while I figure out something that changes the way I use it completely. Recently I came up with a way to simplify views by delegating more logic to the templates.

Normally, my view’s render method would just pass the model’s attributes to the template.

// Backbone View
Backbone.View.extend({
    initialize: function() {
        this.model = {}; // Backbone Model
        this.template = ''; // Handlebars Template           
    },

    render: function() {
        this.$el.html(this.template(this.model.toJSON()))
    }
});
// Backbone Model
Backbone.Model.extend({
    defaults: {
        firstName: '',
        lastName: '',
        hobbies: []
    }
});
<!-- Handlebars Template -->
<span class="name">{{firstName}} {{lastName}}</span>

<ul class="hobbies">
    {{#each hobbies}}
        <li>{{this}}</li>
    {{/each}}
</ul>

This is ok for simple models, but sometimes I use getter methods that use multiple attributes in order to output more detailed data. In this case I normally have a getRenderData method in my views that prepares the data for the template to render.

// Backbone Model
Backbone.Model.extend({
    defaults: {
        firstName: '',
        lastName: '',
        hobbies: []
    },

    getFullName: function() {
        return this.get('firstName') + ' ' + this.get('lastName');
    }
});
// Backbone View
Backbone.View.extend({
    initialize: function() {
        this.model = {}; // Backbone Model
        this.template = ''; // Handlebars Template
    },

    getRenderData: function() {
        var data = {
            fullName: this.model.getFullName()
        };

        return _.extend({}, this.model.toJSON(), data);
    },

    render: function() {
        this.$el.html(this.template(this.getRenderData()))
    }
});
<!-- Handlebars Template -->
<span class="name">{{fullName}}</span>

<ul class="hobbies">
    {{#each hobbies}}
        <li>{{this}}</li>
    {{/each}}
</ul>

As you can see, things start to get tedious when there are multiple getter methods in the model. I wanted to find a way to reduce the amount of code and redundancy.

So after some brainstorming I came up with an idea… Why not pass the model to the template? Instead of preparing the data for the template in my views, the template has access to the model and therefore can get anything from it. For this to work I came up with a couple of Handlebar helpers.

// Gets an attribute from a model. 
//
// {{get "hobbies"}} 
// {{get "firstName"}}

Handlebars.registerHelper('get', function (attr) {
    return this.get(attr);
});
// Executes a method from a model.
//
// {{method "getFullName"}}        

Handlebars.registerHelper('method', function (method) {
    return this[method];
});
// The equivalent of handlebar's `with` helper 
// but using a model's attribute.
//
// {{#using "hobbies"}}
//   {{#each hobbies}}{{this}}{{/each}}
// {{/using}}

Handlebars.registerHelper('using', function (attr, options) { 
    var data = {};
    data[attr] = this.get(attr);
    return options.fn(data);
});

With these helpers I can simplify my views.

// Backbone View
Backbone.View.extend({
    initialize: function() {
        this.model = {}; // Backbone Model
        this.template = ''; // Handlebars Template
    },

    render: function() {
        this.$el.html(this.template(this.model));
    }
});  
<!-- Handlebars Template -->
<span class="name">{{method "getFullName"}}</span>

<ul class="hobbies">
    {{#using "hobbies"}}
        {{#each hobbies}}
            <li>{{this}}</li>
        {{/each}}
    {{/using}}
</ul>   

I haven’t use or even test out these helpers to see if they work. This was just an idea I wanted to get out of my head before I forget it. I’m kind of sold on this approach, but I need to try it out before jumping to conclusions so when I do, I’ll report back with the results.