34

Is there a best-practice or common way in JavaScript to have class members as event handlers?

Consider the following simple example:

<head>
    <script language="javascript" type="text/javascript">

        ClickCounter = function(buttonId) {
            this._clickCount = 0;
            document.getElementById(buttonId).onclick = this.buttonClicked;
        }

        ClickCounter.prototype = {
            buttonClicked: function() {
                this._clickCount++;
                alert('the button was clicked ' + this._clickCount + ' times');
            }
        }

    </script>
</head>
<body>
    <input type="button" id="btn1" value="Click me" />
    <script language="javascript" type="text/javascript">
        var btn1counter = new ClickCounter('btn1');
    </script>
</body>

The event handler buttonClicked gets called, but the _clickCount member is inaccessible, or this points to some other object.

Any good tips/articles/resources about this kind of problems?

5 Answers 5

49
ClickCounter = function(buttonId) {
    this._clickCount = 0;
    var that = this;
    document.getElementById(buttonId).onclick = function(){ that.buttonClicked() };
}

ClickCounter.prototype = {
    buttonClicked: function() {
        this._clickCount++;
        alert('the button was clicked ' + this._clickCount + ' times');
    }
}

EDIT almost 10 years later, with ES6, arrow functions and class properties

class ClickCounter  {
   count = 0;
   constructor( buttonId ){
      document.getElementById(buttonId)
          .addEventListener( "click", this.buttonClicked );
  }
   buttonClicked = e => {
     this.count += 1;
     console.log(`clicked ${this.count} times`);
   }
}

https://codepen.io/anon/pen/zaYvqq

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks for your solution. I am also trying to understand the bigger picture - patterns, practices etc. Any good articles you know about?
This is a good example, but this answer might be better with some explanation as to why it's a good solution, for the novices around here.
The ES2015 edit works via Babel but the public field syntax is not allowed in all browsers yet (even now in 2019).
didn't quite work in my application. The underlying class is built by a factory, which also sets the event listener. The 'this' variable when the event fires is then that of the factory. Trying to figure out how addEventListener knows what 'this' is to be used when the event handler is called ..
changed the handler method from clicked(){ ...} to clicked = () => {...} and it works. Because anonymous functions use 'this' differently I gather?
|
17

I don't know why Function.prototype.bind wasn't mentioned here yet. So I'll just leave this here ;)

ClickCounter = function(buttonId) {
    this._clickCount = 0;
    document.getElementById(buttonId).onclick = this.buttonClicked.bind(this);
}

ClickCounter.prototype = {
    buttonClicked: function() {
        this._clickCount++;
        alert('the button was clicked ' + this._clickCount + ' times');
    }
}

4 Comments

Indeed clean and simple solution! +1
this doesn't work. "this.buttonClicked is undefined"
@FabioDelarias it should work, please check the example. codepen.io/anon/pen/QzwRKE?editors=1010
Great solution, and probably the way it's actually supposed to be done.
9

A function attached directly to the onclick property will have the execution context's this property pointing at the element.

When you need to an element event to run against a specific instance of an object (a la a delegate in .NET) then you'll need a closure:-

function MyClass() {this.count = 0;}
MyClass.prototype.onclickHandler = function(target)
{
   // use target when you need values from the object that had the handler attached
   this.count++;
}
MyClass.prototype.attachOnclick = function(elem)
{
    var self = this;
    elem.onclick = function() {self.onclickHandler(this); }
    elem = null; //prevents memleak
}

var o = new MyClass();
o.attachOnclick(document.getElementById('divThing'))

Comments

5

You can use fat-arrow syntax, which binds to the lexical scope of the function

function doIt() {
  this.f = () => {
    console.log("f called ok");
    this.g();
  }
  this.g = () => {
    console.log("g called ok");
  }
}

After that you can try

var n = new doIt();
setTimeout(n.f,1000);

You can try it on babel or if your browser supports ES6 on jsFiddle.

Unfortunately the ES6 Class -syntax does not seem to allow creating function lexically binded to this. I personally think it might as well do that. EDIT: There seems to be experimental ES7 feature to allow it.

Comments

2

I like to use unnamed functions, just implemented a navigation Class which handles this correctly:

this.navToggle.addEventListener('click', () => this.toggleNav() );

then this.toggleNav() can be just a function in the Class.

I know I used to call a named function but it can be any code you put in between like this :

this.navToggle.addEventListener('click', () => { [any code] } );

Because of the arrow you pass the this instance and can use it there.

Pawel had a little different convention but I think its better to use functions because the naming conventions for Classes and Methods in it is the way to go :-)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.