BackboneJS: View renders fine, but refreshes with undefined collection

backbone.js

Solution

As is, `EntryListView.render` is not bound to a specific context, which means that the scope (`this`) is set by the caller : when you click on your button, this is set to your collection.

You have multiple options to solve your problem:

specify the correct context as third argument when applying `on`

initialize: function(){
    this.collection.on('add', this.render, this);
},

bind your `render` function to your view with _.bindAll:

initialize: function(){
    _.bindAll(this, 'render');
    this.collection.on('add', this.render);
},

use `listenTo` to give your function the correct context when called

initialize: function(){
    this.listenTo(this.collection, 'add', this.render);
},

You usually would do 2 or/and 3, `_.bindAll` giving you a guaranteed context, `listenTo` having added benefits when you destroy your views

initialize: function(){
    _.bindAll(this, 'render');
    this.listenTo(this.collection, 'add', this.render);
},

And if I may:

- don't create your main view in a fetch callback, keep it referenced somewhere so you can manipulate it at a later time

- don't declare collections/models on the prototype of your views, pass them as arguments

- don't hardwire your DOM elements in your views, pass them as arguments

Something like

var EntryListView = Backbone.View.extend({
    events: {
        'click button': 'addEntry'
    },
    initialize: function(){
        _.bindAll(this, 'render');
        this.listenTo(this.collection, 'reset', this.render);
        this.listenTo(this.collection, 'add', this.render);
    },
    render: function(){
        var els = [];
        this.collection.each(function(item){
            els.push(new EntryView({model:item}).render().el);
        });

        this.$el.empty();
        this.$el.append(els);
        this.$el.append('<button>New</button>');
        return this;
    },
    addEntry: function(){
        entries.add(new Entry({
            title: "New entry", 
            text: "This entry was inserted after the view was rendered"
        }));
    }
});

var view = new EntryListView({
    collection: entries,
    el: '#entries'
});
view.render();

entries.fetch({reset: true});

And a demo http://jsbin.com/opodib/1/edit

Problem

I am messing around with Backbone some weeks now and made some simple applications based on tutorials. Now I started from scratch again and tried to use the nice features Backbone offers as I am supposed to. My view gets in the way though. When the page loads, it renders fine and creates its nested views by iterating the collection. When I call render() again to refresh the whole list of just a single entry, all of the views attributes seem to be undefined. The model of a single entry: ``` Entry = Backbone.Model.extend({ }); ``` A list of entries: (json.html is placeholder for dataside) ``` EntryCollection = Backbone.Collection.extend({ model: Entry, url: 'json.html' }); var entries = new EntryCollection(); ``` View for a single entry, which fills the Underscore template and should re-render itself, when the model changes. ``` EntryView = Backbone.View.extend({ template: _.template($('#entry-template').html()), initialize: function(){ this.model.on('change', this.render); }, render: function(){ this.$el.html(this.template(this.model.toJSON())); return this; } }); ``` View for the whole list of entries which renders a EntryView for each item in the collection and should re-render itself, if a new item is added. The button is there for testing purposes. ``` EntryListView = Backbone.View.extend({ tagName: 'div', collection: entries, events: { 'click button': 'addEntry' }, initialize: function(){ this.collection.on('add',this.render); }, render: function(){ this.$el.append('<button>New</button>'); //to test what happens when a new item is added var els = []; this.collection.each(function(item){ els.push(new EntryView({model:item}).render().el); }); this.$el.append(els); $('#entries').html(this.el); return this; }, addEntry: function(){ entries.add(new Entry({ title: "New entry", text: "This entry was inserted after the view was rendered" })); } }); ``` Now, if I fetch the collection from the server, the views render fine: ``` entries.fetch({ success: function(model,response){ new EntryListView().render(); } }); ``` As soon as I click the button to add an item to the collection, the event handler on EntryListView catches the 'add' event and calls render(). But if I set a breakpoint in the render function, I can see that all attributes seem to be "undefined". There's no `el`, there's no `collection`... Where am I going wrong? Thanks for your assistance, Robert

Original source