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

3

33

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/

Prologize answered 1/5, 2017 at 22:39 Comment(1)
Note that the with statement is strongly discouraged, and is disallowed in Strict mode. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…Wylen
W
62

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);
Woodchuck answered 1/5, 2017 at 22:47 Comment(8)
thank you for the bind-function. I guess proper oop style would be to bind in the method declaration this.OnEvent = OnEvent.bind(this); :-) Binding at the end of the definition will freeze (only) Microsoft Edge 38.14393.0.0 :-( see here: jsfiddle.net/sepfsvyo/3Prologize
if you added a couple of these event handlers, how would you go about removing one of them? it seems with anon functions you loose ability to turn off this.OnEvent later.?Vasty
@Vasty I was goint to add a note about it when I posted the answer, but felt there is no need for that. Now that I have a second opinion about it, I added the note.Woodchuck
@ibrahimmahrir ah, that's what I was missing. so in a class, window.addEventListener('message', this.msgref= function(e){that.handleMessage(e)}) will it work?Vasty
@Vasty yes, If this is the instance of the class (if that line of code is in a method or constructor, not in a another functions that has its this bound to something else).Woodchuck
@Vasty So for example, in his code he's doing this: this.OnEvent = OnEvent;. Instead he coud just do this: r.addEventListener('mouseover', this.OnEvent = OnEvent.bind(this));.Woodchuck
@ibrahimmahrir thanks. I guess to my shame I've mostly just added events without cleaning up... I wasn't sure how to get a reference while still controlling thisVasty
@Vasty Attaching event to method is tricky, everything wants to take controll of this.Woodchuck
A
15

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.

Autoradiograph answered 28/9, 2019 at 18:50 Comment(3)
thks to pointing me to this . I never heard about handleEvent before and this is really awesome. It works perfectly !Assuntaassur
I find this cleaner as well.Candidate
For more information on handleEvent, see developer.mozilla.org/en-US/docs/Web/API/EventTarget/… and developer.mozilla.org/en-US/docs/Web/API/EventTarget/…Wylen
W
2

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/

Wylen answered 13/11, 2021 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.