Reputation: 2561
In JavaScript, what is the best way to remove a function added as an event listener using bind()?
Example
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.addEventListener("click", this.clickListener.bind(this));
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", ___________);
};
})();
The only way I can think of is to keep track of every listener added with bind.
Above example with this method:
(function(){
// constructor
MyClass = function() {
this.myButton = document.getElementById("myButtonID");
this.clickListenerBind = this.clickListener.bind(this);
this.myButton.addEventListener("click", this.clickListenerBind);
};
MyClass.prototype.clickListener = function(event) {
console.log(this); // must be MyClass
};
// public method
MyClass.prototype.disableButton = function() {
this.myButton.removeEventListener("click", this.clickListenerBind);
};
})();
Are there any better ways to do this?
Upvotes: 228
Views: 84129
Reputation: 2310
As others have said, bind
creates a new function instance and thus the event listener cannot be removed unless it is recorded in some way.
For a more beautiful code style, you can make the method function a lazy getter so that it's automatically replaced with the bound version when accessed for the first time:
class MyClass {
activate() {
window.addEventListener('click', this.onClick);
}
deactivate() {
window.removeEventListener('click', this.onClick);
}
get onClick() {
const func = (event) => {
console.log('click', event, this);
};
Object.defineProperty(this, 'onClick', {value: func});
return func;
}
}
If ES6 arrow function is not supported, use const func = (function(event){...}).bind(this)
instead of const func = (event) => {...}
.
Raichman Sergey's approach is also good, especially for classes. The advantage of this approach is that it's more self-complete and has no separated code other where. It also works for an object which doesn't have a constructor or initiator.
Upvotes: 1
Reputation: 57318
Although what @machineghost said was true, that events are added and removed the same way, the missing part of the equation was this:
A new function reference is created after
.bind()
is called.
See Does bind() change the function reference? | How to set permanently?
So, to add or remove it, assign the reference to a variable:
var x = this.myListener.bind(this);
Toolbox.addListener(window, 'scroll', x);
Toolbox.removeListener(window, 'scroll', x);
This works as expected for me.
Upvotes: 364
Reputation: 1
can use about ES7:
class App extends React.Component {
constructor(props){
super(props);
}
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange = () => {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
Upvotes: -1
Reputation: 775
We had this problem with a library we could not change. Office Fabric UI, which meant we could not change the way event handlers were added. The way we solved it was to overwrite the addEventListener
on the EventTarget
prototype.
This will add a new function on objects element.removeAllEventListers("click")
(original post: Remove Click handler from fabric dialog overlay)
<script>
(function () {
"use strict";
var f = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.f = f;
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
this._eventHandlers[type].push([fn, capture]);
this.f(type, fn, capture);
}
EventTarget.prototype.removeAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
if (type in this._eventHandlers) {
var eventHandlers = this._eventHandlers[type];
for (var i = eventHandlers.length; i--;) {
var handler = eventHandlers[i];
this.removeEventListener(type, handler[0], handler[1]);
}
}
}
EventTarget.prototype.getAllEventListeners = function (type) {
this._eventHandlers = this._eventHandlers || {};
this._eventHandlers[type] = this._eventHandlers[type] || [];
return this._eventHandlers[type];
}
})();
</script>
Upvotes: 1
Reputation: 1584
jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));
Upvotes: 1
Reputation: 35735
It doesn't matter whether you use a bound function or not; you remove it the same way as any other event handler. If your issue is that the bound version is its own unique function, you can either keep track of the bound versions, or use the removeEventListener
signature that doesn't take a specific handler (although of course that will remove other event handlers of the same type).
(As a side note, addEventListener
doesn't work in all browsers; you really should use a library like jQuery to do your event hook-ups in a cross-browser way for you. Also, jQuery has the concept of namespaced events, which allow you to bind to "click.foo"; when you want to remove the event you can tell jQuery "remove all foo events" without having to know the specific handler or removing other handlers.)
Upvotes: 2
Reputation: 724
For those who have this problem while registering/removing listener of React component to/from Flux store, add the lines below to the constructor of your component:
class App extends React.Component {
constructor(props){
super(props);
// it's a trick! needed in order to overcome the remove event listener
this.onChange = this.onChange.bind(this);
}
// then as regular...
componentDidMount (){
AppStore.addChangeListener(this.onChange);
}
componentWillUnmount (){
AppStore.removeChangeListener(this.onChange);
}
onChange () {
let state = AppStore.getState();
this.setState(state);
}
render() {
// ...
}
}
Upvotes: 58
Reputation: 4737
Here is the solution:
var o = {
list: [1, 2, 3, 4],
add: function () {
var b = document.getElementsByTagName('body')[0];
b.addEventListener('click', this._onClick());
},
remove: function () {
var b = document.getElementsByTagName('body')[0];
b.removeEventListener('click', this._onClick());
},
_onClick: function () {
this.clickFn = this.clickFn || this._showLog.bind(this);
return this.clickFn;
},
_showLog: function (e) {
console.log('click', this.list, e);
}
};
// Example to test the solution
o.add();
setTimeout(function () {
console.log('setTimeout');
o.remove();
}, 5000);
Upvotes: 0
Reputation: 37338
It's been awhile but MDN has a super explanation on this. That helped me more than the stuff here.
MDN :: EventTarget.addEventListener - The value of "this" within the handler
It gives a great alternative to the handleEvent function.
This is an example with and without bind:
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:
Upvotes: -3
Reputation: 349
If you want to use 'onclick', as suggested above, you could try this:
(function(){
var singleton = {};
singleton = new function() {
this.myButton = document.getElementById("myButtonID");
this.myButton.onclick = function() {
singleton.clickListener();
};
}
singleton.clickListener = function() {
console.log(this); // I also know who I am
};
// public function
singleton.disableButton = function() {
this.myButton.onclick = "";
};
})();
I hope it helps.
Upvotes: -1