I'm fairly new to backbone and I try to understand the ins and outs of zombie views.
A zombie is, according to this article:
When we bind objects together through events but we don’t bother unbinding them. As long as these objects are bound together, and there is a reference in our app code to at least one of them, they won’t be cleaned up or garbage collected. The resulting memory leaks are like the zombies of the movies – hiding in dark corners, waiting to jump out and eat us for lunch.
The article mentionned above suggests to create an object that manages the transitions between views and then to implement a close function to remove and unbind the view.
That being said, depending on the situation, where to call that close function from?
I add a property in the initialize block of my parent view to keep a trace of the child view. That way I'm able to call .remove() on it before I replace it by a new one. Is it good practice or is there a better way?
I also don't understand why defining el
and then rendering with
this.$el.html(this.template(this.model.attributes));
doesn't allow me to unbind the view while it works as expected by doing
$('#sportsManDetails').html(this.$el.html(this.template(this.model.attributes)));
As for the exemple, I just created a simple app that displays a list of sportsmen's names and that shows more details when clicking on a name.
Here's the code and a working fiddle:
html
<script id="nameListTemplate" type="text/template">
<%= first %> <%= last %>
</script>
<script id="sportsManDetailsTemplate" type="text/template">
<ul>
<li><%= first %></li>
<li><%= last %></li>
<li><%= age %></li>
<li><%= sport %></li>
<li><%= category %></li>
</ul>
<button class="test">Test</button>
</script>
<div id="sportsMenName"></div>
<div id="sportsManDetails"></div>
JS
model and collection
var app = app || {};
app.SportsManModel = Backbone.Model.extend({});
app.SportsMenCollection = Backbone.Collection.extend({
model: app.SportsManModel
});
NameView
app.NameView = Backbone.View.extend({
tagName: 'li',
className: 'sportsMan',
template: _.template($('#nameListTemplate').html()),
initialize: function(){
this.sportsManDetailsView;
},
events: {
'click': 'showSportsManDetails'
},
showSportsManDetails: function(e){
if (typeof this.sportsManDetailsView !== 'undefined'){
this.sportsManDetailsView.remove();
}
this.sportsManDetailsView = new app.SportsManDetailsView({
model: this.model
})
},
render: function(){
this.$el.append(this.template(this.model.attributes));
return this;
}
});
NameListView
app.NameListView = Backbone.View.extend({
el: '#sportsMenName',
initialize: function(sportsMen){
this.collection = new app.SportsMenCollection(sportsMen);
this.render();
},
render: function(){
this.collection.each(function(sportsMen){
this.renderContact(sportsMen);
}, this);
},
renderContact: function(sportsMen){
var nameView = new app.NameView({
model: sportsMen
});
this.$el.append(nameView.render().el);
}
});
SportsManDetailsView
app.SportsManDetailsView = Backbone.View.extend({
// doesn't work if I use el in conjunction with
// this.$el.html(this.template(this.model.attributes));
// el: '#sportsManDetails',
template: _.template($('#sportsManDetailsTemplate').html()),
initialize: function(){
this.render();
},
events: {
'click .test': 'test'
},
test: function(){
alert('test');
},
render: function(){
// that does not work
//this.$el.html(this.template(this.model.attributes));
// is this good practice?
$('#sportsManDetails').html(this.$el.html(this.template(this.model.attributes)));
}
});
app.js
var sportsMen = [
{first: 'Quentin', last: 'Tarant', age: '34', sport: 'bike', category: '- 90kg'},
{first: 'Aymeric', last: 'McArthur', age: '54', sport: 'jetski', category: '200HP'},
{first: 'Peter', last: 'TheFat', age: '45', sport: 'curling', category: 'dunno'},
{first: 'Charles', last: 'Martel', age: '21', sport: 'Moto', category: 'MX 250cc'},
];
$(function(){
new app.NameListView(sportsMen);
});