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