Husband, frontend @blimp, MMA addict, gamer, occasional guitarist, mystery enthusiast and 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!

3 notes

  1. kano89 reblogged this from hacklr
  2. hacklr posted this