born loser
born loser

Reputation: 430

How adding event handler inside a class with a class-method as the callback?

How do I add an event handler inside a class with a class-method as the callback?

<div id="test">move over here</div>
<script>
    oClass = new CClass();
    function CClass()
    {
        this.m_s = "hello :-/";
        this.OnEvent = OnEvent;
        with(this)
        {
            var r = document.getElementById("test");
            r.addEventListener('mouseover', this.OnEvent);  // this does NOT work :-/
        }
      
        function OnEvent()
        {
            alert(this);    // this will be the HTML div-element
            alert(this.m_s);    // will be undefined :-()
        }
    }
</script>

Yes I know some quirks to make it work but what would be the intended way when these event handlers were introduced ??? I again have the bitter feeling, that no-one truly lives OOP :-(

Here for you to play: https://jsfiddle.net/sepfsvyo/1/

Upvotes: 34

Views: 37802

Answers (3)

Manngo
Manngo

Reputation: 16281

The answer from @ibrahimmahrir does the job, but I wanted to consolidate a few points.

As many JavaScript developers struggle to understand, the this keyword is a moving target. In traditional OOP languages, an object method is exclusive to the object, so this is defined as the object to which it is attached.

JavaScript functions are more promiscuous, and can be attached to multiple objects. In JavaScript, this refers to the object which is currently invoking the function, not necessarily the one to which it was originally attached.

For an Event Handler function, the invoking object is the element to which it is attached, not the original object; thus this refers to the element. The usual safe method is to store a reference to the original object in a different variable, often called that:

oClass = new CClass();
function CClass() {
    var that = this;        //  a reference to the original object
    this.m_s = "hello :-/";
    this.OnEvent = OnEvent;
    var r = document.getElementById("test");
    r.addEventListener('click', this.OnEvent);

    function OnEvent() {
        alert(that);        //  this is now the object
        alert(that.m_s);    //  this works
    }
}

The comments above are my updated comments. I have also removed the with statement which wasn’t contributing much and which is seriously discouraged.

Oh, and I have changed the event to click to make it easier to test.

While we’re on the confusion with this, it is not necessarily the element which started things off. Suppose we now include a span:

<div id="test">click <span>over</span> here</div>

Clicking on the span will trigger the event listener, even though the you didn’t actually click on the div to which it is attached. In this case the event is bubbled from the span to the div.

Here this refers only to the div element with the event listener. If you want to reference the span, you will need event.target:

function OnEvent(event) {   //  include event parameter
    alert(this);            //  the element attached
    alert(event.target);    //  the element clicked
    alert(that);            //  this is now the object
    alert(that.m_s);        //  this works
}

Here is an updated fiddle: https://jsfiddle.net/osk083xv/

Upvotes: 2

ibrahim mahrir
ibrahim mahrir

Reputation: 31682

The this inside the event listener callback will be the element that fired the event. If you want the this to be the instance of your class, then either:

Bind the function to the class instance:

Using Function.prototype.bind, will create a new function that its this value will always be what you specify it to be (the class instance):

r.addEventListener('mouseover', this.OnEvent.bind(this));
//                                          ^^^^^^^^^^^

Wrap the function inside an anonymous function:

var that = this;
r.addEventListener('mouseover', function(ev) { that.OnEvent(ev); });
//                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

or use an arrow function (so no need for that):

r.addEventListener('mouseover', ev => this.OnEvent(ev));
//                              ^^^^^^^^^^^^^^^^^^^^^^

Note: As mentioned in a comment bellow, both of the above methods pass a different function to addEventListener (the one with bind create a new function, and the anounimous function is obviously !== this.OnEvent). If you are going to remove the event listener later, you'll have to store a reference to the function:

var reference;
r.addEventListener('mouseover', reference = this.OnEvent.bind(this));
//                              ^^^^^^^^^^^^

or:

var reference;
var that = this;
r.addEventListener('mouseover', reference = function(ev) { that.OnEvent(ev); });
//                              ^^^^^^^^^^^^

then you can remove the event listener like:

r.removeEventListener('mouseover', reference);

Upvotes: 64

Chalibou
Chalibou

Reputation: 508

You can actually return the object as an EventListener callback, this way JS will search for an handleEvent method in the class and execute accordingly :

var myInstance = new myClass;
myInstance.addEventListener("mousedown",myInstance);

//  To remove the event you can follow the same pattern
myInstance.removeEventListener("mousedown",myInstance);

You have to construct your class this way :

class myClass {
   
    constructor(){
        //  Whatever this is supposed to do.
        //  You can also add events listener within the class this way :
        this.addEventListener("mousedown",this);
    }
    
    mouseDownEvent(e)(){
        //  Some action related to the mouse down event (e)
        console.log(e.target);
    }
    mouseMoveEvent(e)(){
        //  Some action related to the mouse move event (e)
    }
    mouseUpEvent(e)(){
        // Some action related to the mouse up event (e)
    }

    handleEvent(e) {
        switch(e.type) {
            case "mousedown":
                this.mouseDownEvent(e);
            break;
            case "mousemove":
                this.mouseMoveEvent(e);
            break;
            case "mouseup":
                this.mouseUpEvent(e);
            break;
        }
    }
}

Sources :

https://medium.com/@WebReflection/dom-handleevent-a-cross-platform-standard-since-year-2000-5bf17287fd38

https://www.thecssninja.com/javascript/handleevent

https://metafizzy.co/blog/this-in-event-listeners/

I find this method clearer, also while declaring events inside the class this is pretty explicit. Hope I helped someone.

Upvotes: 15

Related Questions