Sophomore Dev

A different approach to rendering Backbone sub-views

I read some good discussion today on Hacker News about how to render subviews in Backbone. There’s no one way to skin this cat, but since I’ve done a fair amount of JavaScript development and have been using Backbone a lot recently, I figured it’s be helpful to put another approach out there since I’ve solved this problem a number of times.

To take the exact problem posited in the blog post, say you have a table that contains a list of names and you want to render the table. You’re planning on having a lot of detail in the individual rows, so it seems like a good idea to split the table view apart from the table row view. Makes perfect sense. Now you want to render the table and you want to delegate to each row to render itself so you’re not interleaving logic. How do you do it?

I put my code below the next few paragraphs because it’s kind of long. Here’s a link to a jsFiddle if you want to play with it: http://jsfiddle.net/N5rUQ/.

Rendering strategy

My strategy for rendering is based on two ideas.

First, it should be intuitive. Event based programming is great and can dramatically simplify things, but only if it’s a conceptual fit. For instance, broadcasting an event saying data has changed and allowing different parts of the UI to react is a great way to decouple code. But, personally, I think of a table as having rows and when a table needs to be rendered, I expect to see the code creating the rows and rendering them in the render method of the table (or not far from it).

Second, JavaScript performance is most impacted by touching the DOM. So you’ll notice I create elements in jQuery, but I never attach them to DOM. I only touch the DOM at the very end when I do this.$el.append(tableEl.children());. That’s a huge performance win. The difference between joining strings and creating elements with jQuery (basically calling document.createElement) is not nearly as significant as the performance hit you get from accessing the DOM repeatedly. I need to find some benchmarks here (or create my own), but I know they exist.

So, when you look at the code below, I hope you find it intuitive. The table creates the sub views for each row, renders them, and then appends that row’s element to a wrapper element that I use to create the final element structure. This is how I’d envision rendering happening in my head, so it’s nice that my mental model matches how the code works.

What do you think? What would you do differently? If you like this post, give it a vote on Hacker News.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
<html>
<head>
<title>Backbone Views</title>
</head>
<body>
<table id="foobar"></table>
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
<script type="text/javascript" src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
<script type="text/javascript" src="http://backbonejs.org/backbone-min.js"></script>
 
<script type="text/javascript">
var Person = Backbone.Model.extend();
 
var PersonCollection = Backbone.Collection.extend({
model: Person
});
 
var TableView = Backbone.View.extend({
 
render: function () {
var self = this,
tableEl = $('<table><thead><tr><th>First name</th><th>Last name</th></tr></thead></table>'),
tbodyEl = $('<tbody />');
 
tableEl.append(tbodyEl);
 
this.rowViews = {};
 
this.model.get('people').each(function (model, index, collection) {
var rowView = new TableRowView({
el: $('<tr />'),
model: model
});
rowView.render();
 
tbodyEl.append(rowView.$el);
self.rowViews[model.id] = rowView;
});
 
this.$el.append(tableEl.children());
}
 
});
 
var TableRowView = Backbone.View.extend({
 
events: {
'click td' : '_onCellClick'
},
 
render: function () {
this.$el.data('id', this.model.id)
.append($('<td />').text(this.model.get('firstName')))
.append($('<td />').text(this.model.get('lastName')));
},
 
_onCellClick: function (e) {
alert('This president is:' + this.model.get('firstName') + ' ' + this.model.get('lastName'));
}
 
});
 
var tableView = new TableView({
 
el: $('#foobar'),
 
model: new Backbone.Model({
people: new PersonCollection([
{ id: 1, firstName: 'Bill', lastName: 'Clinton' },
{ id: 2, firstName: 'Geroge', lastName: 'Bush' },
{ id: 3, firstName: 'Barack', lastName: 'Obama' }
])
})
 
});
tableView.render();
</script>
</body>
</html>

A few additional comments:

  • I’m not using a JS templating engine, instead I’m building HTML with jQuery. If you’d prefer to use something to do the templating, you can see where it would fit in.
  • Notice that I’m also opting not to set an id attribute on the table rows. I really dislike storing information in the id and then having to parse/re-render it constantly. jQuery.data is great for solving this problem and so are data-* attributes. Curious if anyone has a reason to not do this? The only thing I can think of is it’s hard to find a row quickly from the TableView. That’s why I store the this.rowViews map. It’d be great if browsers could add data-* selectors to their selector engines. You can do $('tr[data-id="123"]'), but jQuery does most of the work and might have to access a lot of elements to filter it down, so it could be quite slow.

 

  • EnglishMan

    wow, the amount of code for this is staggering… English is so much better than javascript..

  • Niz

    What you have done is the fastest way to construct a view. There is no other.