Reputation: 349
I'am new to Backbone.js and this problem has really got me stumped.
A view is built up from a collection, the collection results are filtered to place each set of results into their own array and then I make another array of the first items from each array, these are the 4 items displayed.
This works fine the first time the page is rendered but when I navigate away from this page and then go back the page now has 8 items, this pattern of adding 4 continues everytime I revisit the page.
// Locatore List Wrapper
var LocatorPageView = Backbone.View.extend({
postshop: [],
postbox: [],
postboxlobby: [],
postboxother: [],
closestPlaces: [],
el: '<ul id="locator-list">',
initialize:function () {
this.model.bind("reset", this.render, this);
},
render:function (eventName) {
//console.log(this)
// Loop over collecion, assigining each type into its own array
this.model.models.map(function(item){
var posttype = item.get('type').toLowerCase();
switch(posttype) {
case 'postshop':
this.postshop.push(item);
break;
case 'postbox':
this.postbox.push(item);
break;
case 'postbox lobby':
this.postboxlobby.push(item);
break;
default:
this.postother.push(item);
}
return ;
}, this);
// Create a closest Places array of objects from the first item of each type which will be the closest item
if (this.postshop && this.postshop.length > 0) {
this.closestPlaces.push(this.postshop[0]);
}
if (this.postbox && this.postbox.length > 0) {
this.closestPlaces.push(this.postbox[0]);
}
if (this.postboxlobby && this.postboxlobby.length > 0) {
this.closestPlaces.push(this.postboxlobby[0]);
}
if (this.postother && this.postother.length > 0) {
this.closestPlaces.push(this.postother[0]);
}
// Loop over the Closest Places array and append items to the <ul> contianer
_.each(this.closestPlaces, function (wine) {
$(this.el).append(new LocatorItemView({
model:wine
}).render().el);
}, this);
return this;
}
})
// Locator single item
var LocatorItemView = Backbone.View.extend({
tagName:"li",
template:_.template($('#singleLocatorTemplate').html()),
render:function (eventName) {
$(this.el).html(this.template(this.model.toJSON()));
return this;
},
events: {
"click .locator-map": "loadMap"
},
loadMap: function(e) {
e.preventDefault();
// Instantiate new map
var setMap = new MapPageView({
model: this.model,
collection: this.collection
});
var maptype = setMap.model.toJSON().type;
App.navigate('mappage', {trigger:true, replace: true});
setMap.render();
App.previousPage = 'locator';
}
});
window.App = Backbone.Router.extend({
$body: $('body'),
$wrapper: $('#wrapper'),
$header: $('#header'),
$page: $('#pages'),
routes: {
'' : '',
'locator': 'locator'
},
locator:function () {
this.$page.empty(); // Empty Page
this.places = new LocatorPageCollection(); // New Collection
this.placeListView = new LocatorPageView({model:this.places}); // Add data models to the collection
this.places.fetch();
this.$page.html(this.placeListView.render().el); // Append the renderd content to the page
header.set({title: 'Locator'}); // Set the page title
this.$body.attr('data-page', 'locator'); // Change the body class name
this.previousPage = ''; // Set previous page for back button
}
});
Upvotes: 0
Views: 328
Reputation: 8581
This sounds like a classic Zombie View problem. Basically when you do this:
this.model.bind("reset", this.render, this);
in your view, you never unbind it. Thus, the view object is still bound to the model and can't be removed from memory. When you create a new view and reset, you have that listener still active which is why you see the duplicate view production. Each time you close and redo the view, you're accumulating listeners which is why it increases in multiples of 4.
What you want to do is unbind
your listeners when you close out the view and rid your program of binds.
this.model.unbind("reset", this.render, this);
This should eliminate the pesky zombies. I'll add a link with more detailed information when I find it.
UPDATE - added useful references
I also ran into this problem a while back. It's quite the common gotcha with Backbone. @Derick Bailey has a really good solution that works great and explains it well. I've included the links below. Check out some of the answers he's provided in his history regarding this as well. They're all good reads.
Backbone, JS, and Garbage Collection
Upvotes: 1
Reputation: 434595
All the properties in your Backbone.View.extend
argument are attached to the view's prototype. In particular, these properties:
postshop: [],
postbox: [],
postboxlobby: [],
postboxother: [],
closestPlaces: [],
end up attached to LocatorPageView.prototype
so each LocatorPageView
instance shares the same set of arrays and each time you use a LocatorPageView
, you push more things onto the same set of shared arrays.
If you need any mutable properties (i.e. arrays or objects) in your Backbone views, you'll have to set them in your constructor:
initialize: function() {
this.postshop = [ ];
this.postbox = [ ];
this.postboxlobby = [ ];
this.postboxother = [ ];
this.closestPlaces = [ ];
}
Now each instance will have its own set of arrays.
Upvotes: 2