James Howell
James Howell

Reputation: 1442

ES6 Class - Call method from within event handler

I am trying to write an ES6 class for an interactive calendar on my current project.

The Class looks similar to the below:

class Calendar {

constructor (s) {

    this.eventButtons = s.eventButtons;
    this.eventButtons.forEach(button => button.addEventListener('click', this.method1);
    this.eventBoxes = s.eventBoxes;


method1 (e) {
    e.preventDefault();
    this.method2(e.target.href);
}

method2 (url) {
    console.log(url);
}

}


export default Calendar;

I know that the context of the 'this' keyword has changed from the constructor to the button which has been clicked within the method1 function. However I don't know how to keep the context of the button and the constructor within the same function. I tried changing the button event listener code to the following:

this.eventButtons.forEach(button => button.addEventListener('click', this.method1).bind(this);

But this just switches the context of the 'this' keyword to the constructor rather than the button. I need to use both in my function.

Any ideas? I'm hoping this is quite a common issue?

Upvotes: 6

Views: 7243

Answers (6)

Kokodoko
Kokodoko

Reputation: 28128

And if you use ES6, you can also use for of instead of forEach. This prevents creating yet another callback with its own scope. In this code, the keyword 'this' still refers to the original class.

this.eventButtons = s.eventButtons;
for(b of this.eventButtons){
   b.addEventListener('click', () => this.method1);
}

Upvotes: 3

amiramw
amiramw

Reputation: 506

You can use bind to create a partial function:

this.eventButtons.forEach(button => button.addEventListener('click', this.method1.bind(this, button));

It works assuming you change method1 to be:

method1 (button, e) {
  e.preventDefault();
  this.method2(e.target.href);
} 

Upvotes: 2

noahnu
noahnu

Reputation: 3574

You have a few options:

You can bind the methods themselves:

this.method1 = this.method1.bind(this);
this.method2 = this.method2.bind(this);

There's the bind operator if you're using Babel (or some other transpiler). It hasn't been accepted into the standard yet, so I'd be weary of using it. Using the bind operator, you can do the equivalent:

this.method1 = ::this.method1
this.method2 = ::this.method2

The other option is to do what you've done already, just corrected.

You have to bind the method, not the result of the forEach.

this.eventButtons.forEach(button =>
    button.addEventListener('click', this.method1.bind(this)));

or with bind op:

this.eventButtons.forEach(button =>
    button.addEventListener('click', ::this.method1));

Finally, you also have the option of creating a wrapper function using arrow notation for lexical scope:

this.eventButtons.forEach(button =>
    button.addEventListener('click', (...params) => this.method1(...params)));

Upvotes: 3

Guillaume Rochat
Guillaume Rochat

Reputation: 400

You could create a closure that would send the event and the button. The closure would keep the this context and send the button as well

button => button.addEventListener('click', event => this.method1(event, button))

Upvotes: 7

gpanagopoulos
gpanagopoulos

Reputation: 2920

Try using a lambda expression to set the delegate of your event as well. Something like below:

button.addEventListener('click', (e) => { e.preventDefault(); this.method2(); });

Upvotes: 2

Sotiris Kiritsis
Sotiris Kiritsis

Reputation: 3336

Since you are using ES6, have you tried using an arrow function?

An arrow function expression has a shorter syntax than a function expression and does not bind its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

method1 = (e) => {
    e.preventDefault();
    this.method2(e.target.href);
}

Upvotes: 5

Related Questions