11

I have a collection of models that I wish to render in a table view. Each model should be represented by a single row in the table, and this row should be generated using a template. I should be able to attach event handlers to that row (say click), that upon event alert some specific information regarding the model associated with that row.

A common way I've seen similar things to this done is to break each row out into it's own view, and have a parent view (lets say the table in this case) use the row view to generate the html to include in your code. However I can't figure out how this works with templates.

In this case, I can not attach events specifically to the RowView as it has no reference to a dom element (this.el for backbone), it simply returns a string. How can I achieve what I want, whilst using a template to maximum capacity?

The question isn't specifically about events, templating or using nested views, but more about the right way to use Backbone to achieve this kind of output.

Sample code(also in a fiddle):

/** View representing a table */
var TableView = Backbone.View.extend({
    tagName: 'table',
    render: function() {
        var rows = _.map(this.collection.models, function(p) {
            return new RowView({model: p}).render();                        
        });
        $('body').html(this.$el.html(rows.join('')));
    }
});

/** View representing a row of that table */
var RowView = Backbone.View.extend({
    render: function() {
        // imagine this is going through a template, but for now
        // lets just return straight html.
        return '<tr>' + 
                '<td>' + this.model.get('name') + '</td>' + 
                '<td>' + this.model.get('age') + '</td>' +
               '</tr>';
    }
});

var data = [
    {'name': 'Oli', 'age': 25},
    {'name': 'Sarah', 'age': 20}];

/** Collection of models to draw */
var peopleCollection = new Backbone.Collection(data);
var tableView = new TableView({collection: peopleCollection});
tableView.render();

Thank you!

oli
  • 3,541
  • 1
  • 27
  • 34

1 Answers1

11

One way to handle a hierarchy of views is to have each view render its children and append them to its el . The events are then handled by each view, according to its model/collection.

To inject your HTML as the view el and thus control the container element, you can use the setElement method

setElement view.setElement(element)

If you'd like to apply a Backbone view to a different DOM element, use setElement, which will also create the cached $el reference and move the view's delegated events from the old element to the new one.

Your example could be rewritten as

var rowTemplate=_.template("<tr>"+
     "<td class='name'><%= name %></td>"+
     "<td class='age'><%= age %></td>"+
     "</tr>");

/** View representing a table */
var TableView = Backbone.View.extend({
    tagName: 'table',

    initialize : function() {
        _.bindAll(this,'render','renderOne');
    },
    render: function() {
        this.collection.each(this.renderOne);
        return this;
    },
    renderOne : function(model) {
        var row=new RowView({model:model});
        this.$el.append(row.render().$el);
        return this;
    }
});

/** View representing a row of that table */
var RowView = Backbone.View.extend({  
    events: {
        "click .age": function() {console.log(this.model.get("name"));}
    },

    render: function() {
        var html=rowTemplate(this.model.toJSON());
        this.setElement( $(html) );
        return this;
    }
});

var data = [
    {'name': 'Oli', 'age': 25},
    {'name': 'Sarah', 'age': 20}];

/** Collection of models to draw */
var peopleCollection = new Backbone.Collection(data);
var tableView = new TableView({collection: peopleCollection});
$("body").append( tableView.render().$el );

And a Fiddle http://jsfiddle.net/9avm6/5/

nikoshr
  • 32,926
  • 33
  • 91
  • 105
  • awesome. one question, can you elaborate on the _.bindAll call? I understand it's supposed to make 'this' more useful at the time the functions are being called, but what does the call in that form mean? To which events are you binding render and renderOne? Why would you bind renderOne to anything? Thanks so much! – Nicolas78 Nov 20 '12 at 09:26
  • @Nicolas78 _.bindAll guarantees that `this` in render and renderOne will be the view, and not the element that triggered the event, for example. Not very useful here, no binding being set, but render could be called on a collection.reset, and renderOne on a collection.add – nikoshr Nov 20 '12 at 13:46
  • thanks a lot. somehow that didn't become apparent from me just reading the docs for that function. – Nicolas78 Nov 20 '12 at 18:38
  • I am a little disturbed that in such a simple scenario, backbone requires me to write so much code. – Greg Ennis Nov 19 '13 at 03:13