Husband, Hacker @blimp, UI/UX Lover, Built @rroundme, MMA Addict, Gamer, Occasional Guitarist, Paranormal Encyclopedia, Conspiracy Theorist.

Rendering Backbone Collections with DocumentFragment

I’ve been hearing a lot about DocumentFragment lately and how it can improve performance by preventing unnecessary reflows.

A DocumentFragment is a lightweight document object that has no parent. We can use it to insert child nodes without having to touch the DOM tree.

Before knowing about this, my standard approach to render a Backbone Collection was to:

  1. Loop through each model.
  2. Pass the model to a “subview” or “item-view”.
  3. Render the item-view.
  4. Append the item-view DOM node to the View’s element.
render: function() {
    this.collection.each(this.addOne, this);
    return this;
}  

 

addOne: function (item) {
    var itemView = new Backbone.View({
        model: item,
        tagName: 'li'
    });

    this.$el.append(itemView.render().el);
    return this;
}

This solution is simple and gets the job done. The problem here is that every time a model is rendered in the addOne method, it causes a reflow because the DOM tree is being manipulated. This may not seem like a big deal if you are rendering a collection with just couple of models. But what happens when a collection has 100 models or more!? Then you need to start thinking about performance.

To make it work, I assign a fragment to the View so that I can access it in the render and addOne methods. To know when to use the fragment and when to append a single item to the View I used a isRendered flag.

initialize: function() {
    this.fragment = document.createDocumentFragment();
    this.isRendered = false;          
    this.render();
}

Now to render a Collection I:

  1. Loop through each model.
  2. Pass the model to a “subview” or “item-view”.
  3. Render the item-view.
  4. Append the item-view DOM node to the View’s fragment.
  5. Append the fragment to the View’s element.
render: function() {
    this.collection.each(this.addOne, this);
    this.$el.html(this.fragment);
    this.isRendered = true;
    return this;
}

 

addOne: function (item) {
    var itemView = new Backbone.View({
        model: item,
        tagName: 'li'
    });

    if (this.isRendered) {
        this.$el.append(itemView.render().el);
    } else {
        this.fragment.appendChild(itemView.render().el);
    }

    return this;
}

The reason I use the isRendered flag is because if I want to append a single model to the collection after it’s rendered, I can use the same addOne method without having to use the fragment.

initialize: function() {
    this.fragment = document.createDocumentFragment();
    this.isRendered = false;          

    this.listenTo(this.collection, 'add', this.addOne);        

    this.render();
}

Using DocumentFragment to render your Backbone Collections can result in more code but, I can see a significant performance boost as you can see in this test.

image

image

If you want to try or play around with this approach you can do it in the fiddle below!

Mixins in Coffeescript

Coffeescript provides a class keyword which we can use to create “classes” in Javascript. Of course, behind the scenes it’s just syntactic sugar for working with Javascript prototypes.

In issue #452 Jeremy Ashkenas explains why mixins will never be part of coffeescript:

So. JavaScript inheritance of properties through prototypes is only for single inheritance. You can have chains pointing back as long as you like, but there’s only one prototype per-object.

Any “mixin” functionality would be limited to what can be accomplished at runtime through helper functions, as mentioned above. It would be a blind copy of properties onto the object or prototype, not a true inclusion, where subsequent changes to the module would affect the mixed-in objects. For this reason, I don’t think that it’s in the domain of CoffeeScript.

As Jeremy pointed out, mixins in Javascript would just perform a blind copy of properties to your object’s prototype. This can be dangerous and hard to debug if you are not careful. As you can see bellow, mixins in Coffeescript can be useful to separate logic, but can result in unexpected behavior.

# Any class that needs to "extend" multiple classes has 
# extend the Mixin class.

class Mixin
    # This static method copies all the properties from the 
    # provided mixins into your class prototype.
    @use = (mixins...) ->
        for mixin in mixins
            @::[key] = value for key, value of mixin::
        @

class Human
    hasMetal: no
    sayHello: -> 'Hello!'

class Robot
    isHuman: no
    sayHello: -> 'Greetings'

# The Cyborg class extends properties from the Human and Robot
# classes by using the `use` method provided by the Mixin class.
class Cyborg extends Mixin
    @use Human, Robot

    sayHello: -> 'Howdy!'
    hasFleshAndMetal: 'Hell Yeah!'

Vader = new Cyborg()

Vader.hasMetal         # false        
Vader.isHuman          # false        
Vader.hasFleshAndMetal # 'Hell Yeah!'
Vader.sayHello()       # 'Howdy!'

When you extend multiple classes using the Mixin class, the sayHello method gets overwritten depending of the order in which the properties are copied to the prototype. If you call the use method after defining the sayHello method in the Cyborg class the sayHello method in the Vader object will output ‘Greetings’, because the sayHello method from the Robot class would be the last property copied to the Cyborg prototype.

Extending objects and Coffeescript classes

In the code above the use method can only “extend” Coffeescript classes. But if you want to extend both objects and classes you can modify the use method like this:

class Mixin
    @use = (mixins...) ->
        for mixin in mixins
            # Notice that now we expect the mixin to be an object.
            @::[key] = value for key, value of mixin
        @

# So now the Cyborg class can "extend" objects as well as classes.
Options = 
    attackHumans: no
    casualtyLimit: 5

class Cyborg extends Mixin
    # Notice that now instead of passing the class 
    # we pass the prototype.
    @use Human::, Robot::, Options

    sayHello: -> 'Howdy!'
    hasFleshAndMetal: 'Hell Yeah!'

Using extend methods from other libraries

Underscore.js and Lodash.js

If you use underscore.js or lodash.js you can use the _.extend method instead of extending a Mixin class:

class Cyborg
    _.extend this::, Human::, Robot::, Options

    sayHello: -> 'Howdy!'
    hasFleshAndMetal: 'Hell Yeah!'

jQuery

If you use jQuery you can use the $.extend method. The cool thing about using jQuery’s $.extend is that it has a recursive mode, so that if a property is an object then it’s recursively merged instead of being overwritten:

class Cyborg
    $.extend true, this::, Human::, Robot::, Options

    sayHello: -> 'Howdy!'
    hasFleshAndMetal: 'Hell Yeah!'

And there you have it. Mixins can be useful to separate your code into reusable bits, but with the caveat of overwriting properties. So use them wisely!

Revesiting Backbone.js View Rendering

In my last blog post I talked about my experiences with rendering Backbone.js views. In the early days I relied on model.toJSON(), but I realized that most of the time it was overkill, so I started adding a getRenderData method to my views to prepare the data needed to render it. While thinking of ways to automate this process, I though that passing the model directly to the template would reduce the overhead of the getRenderData method.

While this was just an idea, I got a lot of interesting feedback. Some people though it was a good idea but most of the people though this approach added unnecessary logic to the templates. The idea of adding extra logic to templates seemed to alarm a lot of people, so I wanted to give it a try and judge by myself.

Readability

The helpers can be a little cryptic if you are looking at the templates without any context of whats going on.

<div>Name: {{get "firstName"}} {{get "lastName"}}</div>

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

Maybe the templates would be more self explanatory if the helpers were named differently, like getAttribute or withAttribute.

Simplicity

Passing the model directly to the template actually cleans a lot of the code in the view or the model, depending on where your getRenderData method is.

I found that using the helpers is not the simplest solution when you want to use all the attributes in your templates. For that, model.toJSON() would be a simpler solution in my opinion.

using adds two extra lines of code that you wouldn’t have if you passed an object with specific data to the template.

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

Complexity

The idea of adding more logic to a template engine that’s supposed to be logic-less can be counter-productive, but that depends on your definition of logic. To me, the {{get}} and {{using}} helpers do not add any extra or heavy logic to the template because they are only used to get data from the model (they don’t change the model).

{{method}} is a different story because it depends of what method you call. Calling a method that requires an argument or doesn’t returns any value could result in dangerous or unwanted results. So this helper isn’t that practical.

After using the helpers I came to the conclusion that templates should be as simple as possible and focused on markup, not on adding extra functionality for your views, models, and collections.

Performance

I created a test case in jsPerf to see if using the helpers would be faster than preparing the data or using model.toJSON().

imageimage

There isn’t any significant performance gain from using the helpers. Sometimes the helpers were faster and sometimes preparing the data was faster. This leads me to believe that both solutions are equally performant, but when using all the model’s attributes in the templates, model.toJSON() is usually faster.

Conclusion

When I first thought of passing the model directly to the templates I was convinced it was a good idea. After using it and comparing it to what I am currently doing to render my views I realized that it has a lot of caveats and that I’m probably never going to use this.

So there you go! Using Handlebars helpers to get data from a Backbone model in your templates is not a good idea.

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.