'this' doesn't work when assign and bind to a variable - javascript

I'm trying to create a custom module. I'm using prototype to add additional methods. The module will have an event, in which I will have to access a method from the module with 'this'. I will do that using bind.
I ran into a slight problem. I created a method, then using prototype, I assigned the method to a variable with the bind method. The problem is, 'this' isn't the one I passed through bind. Here's the sample code:
JSFiddle
function MyPlugin() {
this.hello = 'hello'
document.addEventListener('click', this.clickedBinded);
this.clickedBinded('e');
}
MyPlugin.prototype.clickedBinded = clicked.bind(this);
function clicked(e) {
console.log(this.hello, e);
}
var plugin1 = new MyPlugin();
How can I get clickedBinded have this as this? Meaning, I don't want this to be Window, I want it to be MyPlugin.
Update
The reason why I did: MyPlugin.prototype.clickedBinded = clicked.bind(this) and not: document.addEventListener('click', this.clicked.bind(this));, is because I'm going to have to remove the event at some point. And as this answer sais, if you're using bind in an event handler, and you want to remove the event, you have to assign the method with bind to a variable.

.bind() has to be called AFTER the object you want it to be bound to has already been created and when you're in the right context to have a reference to the desired object. You are calling .bind() when your code is first loaded. That will not have a meaningful value for this. Instead, change your code to this:
function MyPlugin() {
this.hello = 'hello';
// save bound function so you can use it to remove the listener later
this.fn = this.clicked.bind(this);
document.addEventListener('click', this.fn);
this.clicked('e');
}
MyPlugin.prototype.clicked = function(e) {
console.log(this.hello, e);
}
// method that will remove the event listener
MyPlugin.prototype.stopClick = function() {
document.removeEventListener('click', this.fn);
}
var plugin1 = new MyPlugin();
Notice in this fixed version that .bind(this) is called ONLY when we already have this that corresponds to our current object.
In your version, you were calling clicked.bind(this) when your code was first loaded and this would have a global value which in strict mode would be undefined and in non-strict mode in the browser would be window. The object that you will create with new MyPlugin() doesn't even exist yet when you were calling clicked.bind(this) so there's obviously no way that this could contain the appropriate value.
If you want to be able to remove the event listener later, then just store the bound listener so you can use it later.

As jfriend00 explained, you should bind the event listener inside the constructor. Each instance will have a different listener.
If you want to be able to remove the listener, store it in the instance:
var click = document.getElementById('click'),
remove = document.getElementById('remove');
function MyPlugin() {
this.hello = 'hello';
var listener = clicked.bind(this);
click.addEventListener('click', listener);
remove.addEventListener('click', function self() {
click.removeEventListener('click', listener);
remove.removeEventListener('click', self);
});
}
function clicked(event) {
console.log(this.hello);
}
var plugin1 = new MyPlugin();
<input type="button" id="click" value="Click me" />
<input type="button" id="remove" value="Remove listener" />

Related

JavaScript - Correct way to remove event listener added by my class [duplicate]

I am trying to remove an eventListener but it looks like I miss something.
Why is the following code not working, it doesn't remove the event listener from the button.
I also tried binding this to pass the scope, but that didn't work either
class Test {
eventHandler(e) {
console.log(e.target.id)
alert()
// no effect
e.target.removeEventListener("click", this.eventHandler)
// no effect either
document.getElementById(e.target.id).removeEventListener("click", this.eventHandler)
}
constructor() {
let b = document.getElementById("b")
b.addEventListener("click", this.eventHandler)
//b.addEventListener("click", this.eventHandler.bind(this) )
}
}
new Test()
<button id="b">
click me
</button>
Prototype methods as event handlers are a bit problematic, specifically when you need both, the this value bound to the instance, and the reference to the actual event handler function.
By default, the event queue calls the handler in the context of the element the event was bound to. It's easy to change the context, but that provides you to create a new function, which then is used as the event handler, and that function is not the method in the prototype anymore.
If you want to keep the compact class structure, one way is to define the event handler methods as own properties of the instance, they simply can't be inherited. The simplest way would be to define the methods as arrow functions in the constructor.
class Test {
constructor() {
this.eventHandler = e => {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
};
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
}
new Test();
<button id="b">Click me!</button>
The arrow function keeps the reference to the lexical environment it was defined in, and the event queue can't override the context. Now this in the handler function is correctly bound to the instance, and this.eventHandler refers to the function, which was attached to the event.
A slightly less memoryconsuming option would be to use bind when creating the own property, like this:
class Test {
constructor() {
this.eventHandler = this.eventHandler.bind(this);
let b = document.getElementById("b");
b.addEventListener("click", this.eventHandler);
}
eventHandler (e) {
console.log(e.target.id);
e.target.removeEventListener("click", this.eventHandler);
}
}
Here bind creates a new function object, which then calls the method in the prototype, the actual code of the method is not duplicated. This is loosely similar if you wrote:
this.eventHandler = e => Test.prototype.eventHandler.call(this, e);
It's notable, that when defining an own property with the same name an underlying prototype property has, the prototype property is not overridden, it's only shadowed in the instance, and multiple instances of the class will still work as intended.
Another option is to create your own "event model", which creates a wrapper function (like in the very last code example above) for all events, and stores the reference to that function. The wrapper calls the actual handler with call, which can bind the wanted this value to the event handler. The stored function references are used to remove events. Building such a model is not extremely complex, but it provides a bit knowledge of how the this binding and native event model work.
The OP's code does not work for two reasons.
in one case the prototypal eventHandler misses the correct this context.
for a second case of running this.eventHandler.bind(this) one creates a new (handler) function with no saved reference to it. Thus with removeEventHandler one never refers to the correct event handler.
Possible solution ...
function handleTestClickEvent(evt) {
console.log(evt.currentTarget);
console.log(this);
console.log(this.eventHandler);
// remove the instance specific (`this` context) `eventHandler`.
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
class Test {
constructor() {
// create own eventHandler with bound `this` context.
this.eventHandler = handleTestClickEvent.bind(this);
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Another possible approach was the usage of an arrow-function based, thus instance-specific, event-handler. Arrow-functions do not support an explicit this binding. They always refer to the context where they are implemented in.
class Test {
constructor() {
// arrow-function based, thus instance-specific event-handler.
this.eventHandler = evt => {
console.log(evt.currentTarget);
console.log(this);
evt.currentTarget.removeEventListener('click', this.eventHandler);
}
document
.querySelector('#b')
.addEventListener('click', this.eventHandler);
}
}
new Test();
<button id="b">click me</button>
Nevertheless, both approaches show, that the prototypal implementation of a reference-specific event-handler is not the path one should follow.
For the scenario provided by the OP I would pick the 1st solution for it provides code reuse by the locally implemented handleTestClickEvent. It also comes with a smaller footprint regarding the instance specific this.eventHandler which for the former gets created from handleTestClickEvent.bind(this) whereas the 2nd solution provides a full handler implementation to every single instance.

Which of these forms of passing a lambda to jQuery's document.ready event is the correct one?

I have an object called foo like so in which I have encapsulated by event handler for document.ready event of jQuery.
var foo = new function()
{
var OnDocumentReady = function() { ... }
}
However, upon trying any of these forms, my event handler is not called.
$(document).ready(foo.OnDocumentReady);
$(document).ready(foo().OnDocumentReady);
$(document).ready(foo()["OnDocumentReady"]);
That is because you have locally scoped OnDocumentReady to the variable environment inside of foo. You need to actually attach it as a property of foo. You can do that with this.
var foo = new function()
{
this.OnDocumentReady = function() { ... }
}
And now your first approach will work:
$(document).ready(foo.OnDocumentReady);
The other two will not work because using new function() is going to construct an object, which is not a function. Calling an object as a function will cause an exception.

The correct way to bind this?

I'm trying to access 'this' inside a method that is called from a button press, where this refers to both the class and the button pressed:
p.myVar = 'banana';
$('.go').on('click', this._init);
p._init = function(e){
//get the button pressed
//get this.myVar
};
To do this I bind this:
$('.go').on('click', this._init.bind(this));
The above works and I can now access my var via:
this.myVar; //banana
But I can no longer access the button.
How can I access it, use e.currentTarget or is there a better way?
You should use the data argument :
$('.go').on('click', {myVar:'banana'}, this._init);
p._init = function(e){
// use e.data.myVar;
// this is the right matching clicked element
};
I presume your declaring the event listener in a closure, if so you can use a local variable and pass it that, the reference is unique to the closure and can be accessed by the function in the listener when it is called. It becomes a kind of invisible global, the reference only exists to that specific call and the listener function but is still shared.
function initButtons(){
var selfRef = this;
$('.go').on('click',selfRef._init);
}

javascript passing event in closure

I was trying the following:
f.addEventListener('submit',(function(frm){
var func = (function(e){somefunction(e,frm);})(e);
})(f),false);
But this is failing. I want to pass the form (f) as a static reference and the dynamic event object to the named function 'somefunction'.
What I have above isnt working, what is the right syntax for passing both?
The issue is that each of the functions is being called right away, with undefined actually being passed to addEventListener().
You'll want to instead return one of the functions without its calling parenthesis so the event can call it later:
f.addEventListener('submit', (function (frm) {
return function (e) {
someFunction(e, frm);
};
})(f), false);
Though, with event bindings, you may not necessarily need the closure, as the <form> will be the context (this) of the function passed:
f.addEventListener('submit', someFunction, false);
function someFunction(e) {
var frm = this;
// ...
}
not saure exactly what you are trying to do but, to looks like you are trying to manually pass in the form via the event handler. Instead save a reference and just refer to it in the handler such as
f.addEventListener('submit',function(){
var func = function(e){
somefunction(e,f);
};
},false);
you shouldn't need the self executing functions unless I am missing your intent here

How do I pass a JavaScript class instance to DOM Event Listener?

I have what seems to be a very tricky situation. I would like to pass an instance of an object to the event listener of a DOM element that was created by that same object instance (if that makes sense).
function Object(callback){
this.callback = callback;
this.node = document.createElement('div');
this.send = function(){
document.getElementById('list').appendChild(this.node);
}
this.node.addEventListener('click',function(){/*this.callback() of Object instance needs to go here*/},true);
}
I know that using callback() would work inside the event listener, but thats not what I need because I will be using variables from the instance that are not passed from the construct later on.
How can I solve this?
The anonymous function changes the meaning of this. To be able to use it within the handler, use another var, or don't create another function:
var elem = this;
this.node.addEventListener('click',function(){ elem.callback(); },true);
or
this.node.addEventListener('click', this.callback, true);

Categories

Resources