Impossible Backbone Zombies
Solution 1:
One problem I see is that your close function never removes the event delegator from the view's el. A view's events are handled by using the delegator form of jQuery's on to attach a single event handler to the view's el. Your close does:
this.$el.empty();
but that only removes the content and any event handlers attached to that content, it does nothing at all to the handlers attached directly to this.el. Consider this minimal example:
var V = Backbone.View.extend({
events: {
'click': 'clicked'
},
clicked: function() {
console.log('still here');
}
});
var v = new V({ el: '#el' });
v.close();
After that, clicking on #el will throw a 'still here' in the console even though you think that the view has been fully cleaned up. Demo: http://jsfiddle.net/ambiguous/aqdq7pwm/
Adding an undelegateEvents call to your close should take care of this problem.
General advice:
Don't use the old-school
onandofffunctions for events, uselistenToandstopListeninginstead.listenTokeeps track of the events on the listener so it is easier to remove them all later.Simplify your
closeto just this:Backbone.View.prototype.close = function() { if(this.onClose) this.onClose(); if(this.views) _.invoke(this.views, 'close'); this.remove(); };Don't bind views to existing
els. Let the view create (and own) its owneland let the caller place thatelinto a container with the usual:var v = new View(); container.append(v.render().el);pattern. If you must attach to an existing
elthen the view should overrideremovewith a slightly modified version of the standard implementation:remove: function() { this.$el.empty(); // Instead of removing the element. this.undelegateEvents(); // Manually detach the event delegator. this.stopListening(); return this; }
Solution 2:
I'm pretty sure I found the root for my problem.
mu is too short was right, with the close() method I wasn't removing the events bound directly to my el (which I tried to do by this.off() - this.$el.off()/this.undelegateEvents() is the correct way). But for me, it only fixed the problem that events got called multiple times unnecessarily.
The reason I was plagued by 'zombie views' or unintended behavior was that I wasn't freeing up the memory in the View..
this.remove() only gets rid of the el and it's elements/events, but not the View's internal variables. To elaborate - in my View I have an array declared like so this.array: [] and I didn't have it freed in the onClose function.
All I had to do was empty it in the onClose function or initially declare the array as this.array: null so on recurrent View renderings it would at least free the previous array (it still should be freed on the onClose method though, because the array/object is still going to sit in the memory until browsing away from the page).
It was excruciating to debug, because it's a crossword game (at least my code is hard to read there) and sometimes the words didn't match up, but I didn't know where the problem was coming from.
Lessons learned.
Post a Comment for "Impossible Backbone Zombies"