Cannot removeEventListener HTML5 Canvas [duplicate] - javascript

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?

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.

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() {
// ...
}
}

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.)

jQuery solution:
let object = new ClassName();
let $elem = $('selector');
$elem.on('click', $.proxy(object.method, object));
$elem.off('click', $.proxy(object.method, object));

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>

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);

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.

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.

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() {
// ...
}
}

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:

Related

Is it more efficient to use a common empty function instead of creating a new one in each class instance?

Let's say I have a class that is designed to have some callbacks added to it later on.
function myclass() {
this.onSomething = function () {};
this.onOtherThing = function () {};
this.something = function () {
// stuff
this.onSomething();
};
this.otherThing = function () {
// other stuff
this.onOtherThing();
};
}
I can't have this.onSomething and this.onOtherThing being undefined or null because when they are called in something() and otherThing(), an error will be thrown, stating that their type is not a function.
Since those empty functions are needed, but they use memory, is the class going to be more memory efficient if I did this?
function myclass() {
this.onSomething = empty;
this.onOtherThing = empty;
...
}
function empty() {
}
This way each class instance's properties point to the same empty function, instead of creating new functions every time. I assume defining an empty method doesn't take a lot of memory, but still... is this technically better?
You are right about the fact that a new function is created for every instance of your class. In order to have this shared across all instances you can declare it on the prototype of the class:
var MyClass = function() {
this.something = function () {
// stuff
this.onSomething();
};
this.otherThing = function () {
// other stuff
this.onOtherThing();
};
}
MyClass.prototype.onSomething = function() {};
MyClass.prototype.onOtherThing = function() {};
This way, the methods will be shared by all instances.
why don't you try to return true or return false instead of returning empty functions.
or best you can use :
function myclass() {
this.onSomething = false;
this.onOtherThing = false;
...
}
as per your comment you can try :
function myclass() {
this.onSomething = empty();
this.onOtherThing = empty();
... }
function empty() {
//return something
return true;
}

How to register an onclick event so it knows the object it is called from?

I have some simple HTML & Javascript code:
<span id="test">Hej</span>
function Hello(el)
{
var el = document.getElementById('test');
this.test = function(fun)
{
el.addEventListener('click', function(e)
{
fun(e);
}, false);
}
}
var hello = new Hello;
hello.test(function()
{
console.log(this);
});
I would like to use "this" in console.log() but I want it to refer to the instance of hello.
How should I change the definition of Hello?
You can use Function#call, which lets you bind this to whatever you want:
function Hello(el)
{
var el = document.getElementById('test');
var that = this;
this.test = function(fun)
{
el.addEventListener('click', function(e)
{
fun.call(that, e);
}, false);
}
}
See this jsfiddle.
In your this.test function replace:
fun(e);
With:
fun.call(that, e);
And add the following before your event listener:
var that = this;
You need to pass it to the passed function. The method addEventListener already provides you with access to the calling element through the usage of the this keyword in the specified listener function. So in order for this object to make it to your "fun" function, it needs to be passed as a variable.
function Hello(el)
{
var el = document.getElementById('test');
this.test = function(fun)
{
el.addEventListener('click', function(e)
{
fun(e, this); // Adding the parameters to pass
}, false);
}
}
var hello = new Hello();
hello.test(function(event, el) // The passed function should be ready to receive it
{
alert(el.innerText);
});
I tested it with this fiddle.
Edit: Not sure I read the question fully the first time, but if you want to have access to Hello from within the function, you'll need to provide an instance of it from within your "class definition".
function Hello(el)
{
var self = this;
var el = document.getElementById('test');
this.test = function(fun)
{
el.addEventListener('click', function(e)
{
fun(e, self); // Adding the parameters to pass
}, false);
}
this.someProperty = "to test the value";
}
var hello = new Hello();
hello.test(function(event, obj) // The passed function should be ready to receive it
{
alert(obj.someProperty);
});
I tested this second version with this fiddle update.

Getting what the current class is declared as

I am writing a class which manipulates some innerHTML & onclick tags of a div, which puts some function in it that is calls something within the class, however, as there are going to be more than one use of the class on a single page.
I was wondering if it was possible within the class to workout what that particular object had been labelled?
function Bob() {
this.test = function()
{
alert('test');
}
this.Bob = function()
{
element.onClick = function() {(some piece of code).test();};
element.innerHTML = "Test";
}
}
function Bob() {}
Bob.prototype.test = function () {
/* do things */
};
Bob.prototype.Bob = function () {
element.addEventListener("click", function () {
this.test();
}.bind(this));
toArray(element.childNodes).forEach(function (node) {
element.removeChild(node);
});
var button = document.create("button");
button.classList.add("button-style");
element.appendChild(button);
button.addEventListener("click", function () {
this.test();
}.bind(this));
};
You want to use .bind to bind functions to a `thisContext
function Bob() {
var self = this;
this.test = function()
{
alert('This is the Bob class');
}
element.onClick = function() {
self.test();
};
}
Due to how closures work the "self" variable will still be inscope within that element's onclick even after the Bob constructor returns.
Simply have different classes return different alert messages inside the test function

How do I find the instance to which a method belongs?

// minified base class code (if the uncompressed code is needed, I'll post it)
function Class(){}Class.prototype.construct=function(){};Class.extend=function(c){var a=function(){arguments[0]!==Class&&this.construct.apply(this,arguments)},d=new this(Class),f=this.prototype;for(var e in c){var b=c[e];if(b instanceof Function)b.$=f;d[e]=b}a.prototype=d;a.extend=this.extend;return a};
// custom event class
var Event = Class.extend({
handlers: [],
// stores the event handler
subscribe: function(handler, thisObj) {
this.handlers.push([handler, thisObj]);
},
// calls the event handlers
fire: function() {
for(i in this.handlers) {
var handler = this.handlers[i];
handler[0].apply(handler[1]);
}
}
});
var Class2 = Class.extend({
myEvent: new Event(), // the event
test: function() { // fires the event
this.myEvent.fire(this);
}
});
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
model.myEvent.subscribe(this.handler, this); // subscribe to the event
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
var instance1 = new Class2();
var instance2 = new Class3(instance1);
instance1.test();
The only way to make the event handler code to work with the good 'this' is by adding a new argument ('thisObj') to the 'subscribe' method? Is there a better way to do this?
The behaviour you're getting is due to the fact that when you pass a "method" to a function, the receiving function has no idea it's a method. It's just a block of javascript that needs to get executed.
Prototype gets around this issue with the bind method
http://api.prototypejs.org/language/function/prototype/bind/
You can get similar behaviour (I didn't look at the implementation details of bind) by using a closure.
var Class3 = Class.extend({
construct: function(model) {
this.name = "abc";
//model.myEvent.subscribe(this.handler, this); // subscribe to the event
var self = this;
model.myEvent.subscribe(function() {self.handler()});
},
handler: function() {
alert(this.name); // alerts 'abc'
}
});
Or apply some similar functionality to the subscribe method of your custom event class.
EDITED To reflect CMS's observations. Thanks!

How to use keyword this in a mouse wrapper in right context in Javascript?

I'm trying to write a simple wrapper for mouse behaviour. This is my current code:
function MouseWrapper() {
this.mouseDown = 0;
this.OnMouseDownEvent = null;
this.OnMouseUpEvent = null;
document.body.onmousedown = this.OnMouseDown;
document.body.onmouseup = this.OnMouseUp;
}
MouseWrapper.prototype.Subscribe = function (eventName, fn) {
// Subscribe a function to the event
if (eventName == 'MouseDown') {
this.OnMouseDownEvent = fn;
} else if (eventName == 'MouseUp') {
this.OnMouseUpEvent = fn;
} else {
alert('Subscribe: Unknown event.');
}
}
MouseWrapper.prototype.OnMouseDown = function () {
this.mouseDown = 1;
// Fire event
$.dump(this.OnMouseDownEvent);
if (this.OnMouseDownEvent != null) {
alert('test');
this.OnMouseDownEvent();
}
}
MouseWrapper.prototype.OnMouseUp = function () {
this.mouseDown = 0;
// Fire event
if (this.OnMouseUpEvent != null) {
this.OnMouseUpEvent();
}
}
From what I gathered it seems that in MouseWrapper.prototype.OnMouseUp and MouseWrapper.prototype.OnMouseDown the keyword "this" doesn't mean current instance of MouseWrapper but something else. And it makes sense that it doesn't point to my instance but how to solve the problem?
I want to solve the problem properly I don't want to use something dirty.
My thinking:
* use a singleton pattern (mouse is only one after all)
* pass somehow my instance to OnMouseDown/Up - how?
Thank you for help!
The above solution is fine but a more re-usable way is to "bind" your method to a context by creating a method that creates that closure for you. For example
Function.bind = function(method, context) {
return function() {
return method.apply(context, arguments);
}
}
Following Slaks's example, you would have:
document.body.onmousedown = Function.bind(this.OnMouseDown,this);
document.body.onmouseup = Function.bind(this.OnMouseUp, this);
or you could do it to Function.prototype for syntactic sugar.
Function.prototype.bind = function(context) {
return function() {
// since this is a prototype method, "this" is the method to be called
return this.apply(context, arguments);
}
}
document.body.onmousedown = this.OnMouseDown.bind(this);
document.body.onmouseup = this.OnMouseUp.bind(this);
You could also bind arguments to be called as well as the context... ask if you're interested.
The singleton approach, though it works, is a hack. It's adding globals instead of properly addressing the issues at hand. It means if you needed the behavior in two places, it wouldn't work.
In Javascript, unlike most languages, the value of the this keyword is determined when the function is called.
For example:
var dumper = function() { alert(this); };
var str1 = "A";
str1.dump = dumper;
str1.dump(); //Alerts A
var str2 = "B";
str2.dump = str1.dump;
str2.dump(); //Alerts B
When the browser calls your event handler, it calls it in the context of the DOM element, so this isn't what you think it is.
To work around this, you need to use an anonymous method that calls your handlers in the right context.
For example:
var self = this;
document.body.onmousedown = function(e) { return self.OnMouseDown(e); };
document.body.onmouseup = function(e) { return self.OnMouseUp(e); };
Also, you should not handle events like this.
Instead, call attachEvent / addEventListener.

Categories

Resources