In the following code:
HTML
<div id="myView">
<button id="test_button">
Test Button
</button>
<ul id="output"></ul>
</div>
JavaScript
var myView = Backbone.View.extend({
initialize: function() {
// why doesn't this remove the previously delegated events?
this.undelegateEvents();
this.delegateEvents({
'click #test_button': 'buttonClicked'
});
},
// this event fires twice for one button click
buttonClicked: function() {
$("#output").append('<li>Button was clicked</li>');
}
});
$(document).ready(function(){
new myView({el: "#myView"});
// instantiate view again
new myView({el: "#myView"});
});
why does
this.undelegateEvents();
in the initialize() method of the Backbone View not remove the previously delegated events from the previous instantiation of the View?
JSFiddle example of above code: https://jsfiddle.net/billb123/o43zruea/28/
I'll try not to shout but please stop trying to bind views to existing elements. Let the view create and own its own el
, then call view.remove()
to kill it off before replacing it. This simple change solves so many problems with view events that you should always think twice (and twice more) if you don't do it this way.
In your case, you'd have HTML like this:
<script id="t" type="text/x-underscore">
<div id="myView">
<button id="test_button">
Test Button
</button>
</div>
</script>
<div id="container">
</div>
<ul id="output"> <!-- This is outside the container because we're going to empty and refill it -->
</ul>
And your JavaScript would look like this:
var myView = Backbone.View.extend({
events: {
'click #test_button': 'buttonClicked'
},
render: function() {
this.$el.html($('#t').html());
return this;
},
buttonClicked: function() {
$("#output").append('<li>Button was clicked</li>');
}
});
$(document).ready(function(){
var v = new myView();
$('#container').append(v.render().el);
v.remove(); // <----------------- Clean things up before adding a new one
v = new myView();
$('#container').append(v.render().el);
});
Points of interest:
remove
on the view when you're done with it.el
.delegateEvents
or undelegateEvents
calls anywhere. The presence of those almost always point to structural problems in your application IMO.Updated fiddle: https://jsfiddle.net/bp8fqdgm/
But why didn't your attempted undelegateEvents
do anything? undelegateEvents
looks like this:
undelegateEvents: function() {
if (this.$el) this.$el.off('.delegateEvents' + this.cid);
return this;
},
The cid
is unique per view instance so each view instance uses its own unique namespace for events that delegateEvents
binds. That means that this:
this.undelegateEvents();
this.delegateEvents();
is saying:
'.delegateEvents' + this.cid
namespace where cid
is unique for each view instance.delegateEvents
call). These events will be attached using the '.delegateEvents' + this.cid
namespace.So your undelegateEvents
call is removing events but not all of them, only the specific event bindings that that view instance adds are removed.
Your this.undelegateEvents()
call doesn't actually accomplish anything because it is in the wrong place and called at the wrong time. If the new View
caller did the undelegateEvents
call:
var v = new myView({el: "#myView"});
v.undelegateEvents();
new myView({el: "#myView"});
then it would happen in the right place and at the right time. Of course this means that your router needs to keep track of the current view so that it can currentView.undelegateEvents()
at the right time; but if you're doing that then you'd be better off (IMO) taking the approach I outlined at the top of the answer.