I have an email view in my Backbone app. It's currently instantiated in the view
action of my controller. It goes a little like this:
routes: {
'email/:id': email
},
//...
email: function (id) {
var email = new Email({
id: id
});
this.emailView = new EmailView({
model: email
});
email.fetch();
}
Now, the problem is, if I visit one email, then another, I end up creating two separate EmailView
s. This means that, for example, the delete link in the EmailView
is bound to two separate Email
models, so clicking delete will delete both (not a good thing).
I'm looking at two solutions. In one, I'd cache the EmailView
, and update its model. The problem then is that I'd have to re-bind the events
in EmailView
.
The other solution would be to create a new EmailView
as I am at the moment, but unbind the old EmailView.el
's events before replacing it.
Am I going about this the right way? Is there a better way to handle this situation? Cheers in advance.
Create a separate view instance for each model instance. Each time you visit a new email then throw away the the old view and create a new view with the new email instance.
Probably what you have if I am guessing is a list view on the left hand side and an editor on the right. You select the email from the list on the left and you want the email body to appear on the right.
You really want about 5 view classes.
PageView
has_one EmailCollectionView on left
has_one EmailEditorView on right
EmailCollectionView
has_many EmailSummaryViews as vertical list
EmailEditorView
has_one EmailView centered
When you click in the EmailCollectionView you trigger an event which is
picked up by EmailEditorView which throws away it's old instance of EmailView
and renders a new version of EmailView
Something like that anyway
We were originally creating new view objects every time someone would navigate to e.g. "candidates/show", just like the email example. That view bound a handler to the 'reset' event of its persistent model. When we then reset that model from the command line, we would see as many events fire as there were instances of that view. In other words, that view was not being garbage collected, even though the elements it added to the DOM were all completely destroyed.
Our solution was to just be sure to instantiate our top level views once, and then have them make their child views in their initialize methods. Then just re-render them as needed. Then you don't have to worry about the complexity of garbage collection (which, as far as I can tell from our experiments, doesn't happen anyway).
I think that the DOM event handlers should be removed by a call to the view's remove() method - see http://api.jquery.com/remove (which Backbone calls under the covers)
If you override the remove() method to also remove binding to any model events as JohnnyO suggested, garbage collection should take care of removing the view.
I ended up doing overriding the remove() method to handle this:
class EmailView extends Backbone.View
initialize: () ->
@model.bind('change', @render)
render: () =>
# do some stuff
remove: () ->
@model.unbind('change', @render)
super()
You can then use as such in a Router:
routes:
'email/:id': email
//...
email: (id) ->
var email = new Email({
id: id
});
this.emailView.remove() if this.emailView
this.emailView = new EmailView({
model: email
});
email.fetch();
}
This will work assuming that all events in the view are bound to the correct element - that is, that you're using @el (or this.el) in the View and that each view has its own @el/this.el.