I am working on a single-page app using Backbone.js. An issue that has occurred to me is that since one is not reloading the page, that when one creates a instance of a View, then I assume, that the View object will remain in memory for the life of the app. This does not seem very efficient to me, since a particular view may no longer be needed if another route is called. However, a particular View may later need to be ‘displayed’ if one returns to that original route. So the question is, how to best manage views in Backbone with regards to routes?
In my app, many of the views are responsible for displaying a particular ‘page’ and as such share the same DOM element. When one of these ‘page’ views is called, it will replace the content in the DOM element previously put in place by the previous view. Thus the previous view is no longer needed.
Do I need to somehow manually destroy the previous View (or is this somehow handled by the Router object)? Or is it better to leave the views once they have been initialized?
Following sample code shows how views instances are being creating in the Router in the app.
/**
* View - List of contacts
*/
var ListContactsView = Backbone.View.extend({
el: '#content',
template: _.template($('#list-contacts-tpl').html()),
initialize: function() {
_.bindAll(this, 'render');
this.collection = new Contacts();
this.collection.bind('reset', this.render);
this.collection.fetch();
},
render: function() {
this.$el.hide();
this.$el.html(this.template({ contacts: this.collection }));
this.$el.fadeIn(500);
}
});
/**
* View - Display single contact
*/
var DisplayContactView = Backbone.View.extend({
el: '#content',
events: {
'click #delete-contact-button': 'deleteContact'
},
template: _.template($('#display-contact-tpl').html()),
initialize: function() {
_.bindAll(this, 'deleteContact', 'render');
// Create reference to event aggregator object.
if (typeof this.options.id === 'undefined') {
throw new Error('View DisplayContactView initialized without _id parameter.');
}
this.model = new Contact({ _id: this.options.id });
// Add parse method since parsing is not done by collection in this
// instance, as this model is not called in the scope of collection
// Contacts.
this.model.parse = function(response) {
return response.data;
};
this.model.bind('change', this.render);
this.model.fetch();
},
deleteContact: function(id) {
// Trigger deleteContact event.
this.eventAggregator.trigger('deleteContact', id);
},
render: function() {
this.$el.html(this.template({ contact: this.model.attributes }));
}
});
/**
* Page routes
*/
var $content = $('#content');
var ClientSideRouter = Backbone.Router.extend({
routes: {
'browse': 'browse',
'browse/view/:id': 'browseViewContact',
'orgs': 'orgs',
'orgs/:orgName': 'orgs',
'orgs/:orgName/:id': 'orgs',
'contact/add': 'addContact',
'contact/view/:id': 'viewContact',
'contact/delete/:id': 'confirmDelete',
'*path': 'defaultPage'
},
addContact: function() {
// Display contact edit form.
var editContactFormView = new EditContactFormView();
// Display email field in edit form.
},
browse: function() {
var listContactsView = new ListContactsView();
},
browseViewContact: function(id) {
var displayContactView = new DisplayContactView({ id: id });
},
defaultPage: function(path) {
$content.html('Default');
},
home: function() {
$content.html('Home');
},
viewContact: function(id) {
$.ajax({
url: '/contact/view/' + id,
dataType: 'html',
success: function(data) {
$content.html(data);
}
});
}
});
var clientSideRouter = new ClientSideRouter();
Backbone.history.start();
Since Backbone.js has no built in support for view compositions, there are several patterns that you could follow when it comes to keeping track of child views.
Derick Bailey illustrates extending Backbone.View to allow views to
clean up after themselves –
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
Another alternative is to add on child views to a property of the
parent view and manually clean them up when the parent view state is
removed.
var ParentView = Backbone.View.extend({
initialize : function(){
this.childViews = [];
},
render: function(){
this.childViews.push(new ChildView);
}
});
A third alternative is to make the child views subscribe to events
that the parent views trigger, so that they can clean up when the
parent view publishes a “close” event.
Also I noticed from your code that you are actually fetching a model within your child view class. Ideally, I would suggest passing the model as a parameter to the constructor as this decouples the view from the data. It’s more MVC-ish