Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <h2>Partial rendering of views</h2> <p>In order to minimize the full rendering of your DOM hierarchy, you can set up special nodes in your DOM that will reflect updates on a given property.</p> <p>Let's use this simple Underscore template, a list of names:</p> <pre class="lang-html prettyprint-override"><code>&lt;ul&gt; &lt;% _(children).each(function(model) { %&gt; &lt;li&gt; &lt;span class='model-&lt;%= model.cid %&gt;-name'&gt;&lt;%= model.name %&gt;&lt;/span&gt; : &lt;span class='model-&lt;%= model.cid %&gt;-name'&gt;&lt;%= model.name %&gt;&lt;/span&gt; &lt;/li&gt; &lt;% }); %&gt; &lt;/ul&gt; </code></pre> <p>Notice the class <code>model-&lt;%= model.cid %&gt;-name</code>, this will be our point of injection.</p> <p>We can then define a base view (or modify Backbone.View) to fill these nodes with the appropriate values when they are updated:</p> <pre class="lang-js prettyprint-override"><code>var V = Backbone.View.extend({ initialize: function () { // bind all changes to the models in the collection this.collection.on('change', this.autoupdate, this); }, // grab the changes and fill any zone set to receive the values autoupdate: function (model) { var _this = this, changes = model.changedAttributes(), attrs = _.keys(changes); _.each(attrs, function (attr) { _this.$('.model-' + model.cid + '-' + attr).html(model.get(attr)); }); }, // render the complete template // should only happen when there really is a dramatic change to the view render: function () { var data, html; // build the data to render the template // this.collection.toJSON() with the cid added, in fact data = this.collection.map(function (model) { return _.extend(model.toJSON(), {cid: model.cid}); }); html = template({children: data}); this.$el.html(html); return this; } }); </code></pre> <p>The code would vary a bit to accommodate a model instead of a collection. A Fiddle to play with <a href="http://jsfiddle.net/nikoshr/cfcDX/" rel="noreferrer">http://jsfiddle.net/nikoshr/cfcDX/</a></p> <h2>Limiting the DOM manipulations</h2> <p>Delegating the rendering to the subviews can be costly, their HTML fragments have to be inserted into the DOM of the parent. Have a look at this <a href="http://jsperf.com/backbone-js-subviews-rendering" rel="noreferrer">jsperf test comparing different methods of rendering</a></p> <p>The gist of it is that generating the complete HTML structure and then applying views is much faster than building views and subviews and then cascading the rendering. For example,</p> <pre class="lang-html prettyprint-override"><code>&lt;script id="tpl-table" type="text/template"&gt; &lt;table&gt; &lt;thead&gt; &lt;tr&gt; &lt;th&gt;Row&lt;/th&gt; &lt;th&gt;Name&lt;/th&gt; &lt;/tr&gt; &lt;/thead&gt; &lt;tbody&gt; &lt;% _(children).each(function(model) { %&gt; &lt;tr id='&lt;%= model.cid %&gt;'&gt; &lt;td&gt;&lt;%= model.row %&gt;&lt;/td&gt; &lt;td&gt;&lt;%= model.name %&gt;&lt;/td&gt; &lt;/tr&gt; &lt;% }); %&gt; &lt;/tbody&gt; &lt;/table&gt; &lt;/script&gt; </code></pre> <pre class="lang-js prettyprint-override"><code>var ItemView = Backbone.View.extend({ }); var ListView = Backbone.View.extend({ render: function () { var data, html, $table, template = this.options.template; data = this.collection.map(function (model) { return _.extend(model.toJSON(), { cid: model.cid }); }); html = this.options.template({ children: data }); $table = $(html); this.collection.each(function (model) { var subview = new ItemView({ el: $table.find("#" + model.cid), model: model }); }); this.$el.empty(); this.$el.append($table); return this; } }); var view = new ListView({ template: _.template($('#tpl-table').html()), collection: new Backbone.Collection(data) }); </code></pre> <p><a href="http://jsfiddle.net/nikoshr/UeefE/" rel="noreferrer">http://jsfiddle.net/nikoshr/UeefE/</a></p> <p>Note that the jsperf shows that the template can be be split into subtemplates without too much penalty, which would allow you to provide a partial rendering for the rows.</p> <p>On a related note, don't work on nodes attached to the DOM, this will cause unnecessary reflows. Either create a new DOM or detach the node before manipulating it.</p> <h2>Squashing zombies</h2> <p>Derick Bailey wrote an excellent article on the subject of <a href="http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/" rel="noreferrer">eradicating zombie views</a></p> <p>Basically, you have to remember that when you discard a view, you must unbind all listeners and perform any additional cleanup like destroying the jQuery plugin instances. What I use is a combination of methods similar to what Derick uses in <a href="https://github.com/derickbailey/backbone.marionette" rel="noreferrer">Backbone.Marionette</a>:</p> <pre class="lang-js prettyprint-override"><code>var BaseView = Backbone.View.extend({ initialize: function () { // list of subviews this.views = []; }, // handle the subviews // override to destroy jQuery plugin instances unstage: function () { if (!this.views) { return; } var i, l = this.views.length; for (i = 0; i &lt; l; i = i + 1) { this.views[i].destroy(); } this.views = []; }, // override to setup jQuery plugin instances stage: function () { }, // destroy the view destroy: function () { this.unstage(); this.remove(); this.off(); if (this.collection) { this.collection.off(null, null, this); } if (this.model) { this.model.off(null, null, this); } } }); </code></pre> <p>Updating my previous example to give the rows a draggable behavior would look like this:</p> <pre class="lang-js prettyprint-override"><code>var ItemView = BaseView.extend({ stage: function () { this.$el.draggable({ revert: "invalid", helper: "clone" }); }, unstage: function () { this.$el.draggable('destroy'); BaseView.prototype.unstage.call(this); } }); var ListView = BaseView.extend({ render: function () { //same as before this.unstage(); this.collection.each(function (model) { var subview = new ItemView({ el: $table.find("#" + model.cid), model: model }); subview.stage(); this.views.push(subview); }, this); this.stage(); this.$el.empty(); this.$el.append($table); return this; } }); </code></pre> <p><a href="http://jsfiddle.net/nikoshr/yL7g6/" rel="noreferrer">http://jsfiddle.net/nikoshr/yL7g6/</a></p> <p>Destroying the root view will traverse the hierarchy of views and perform the necessary cleanups.</p> <p>NB: sorry about the JS code, I'm not familiar enough with Coffeescript to provide accurate snippets.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload