Search code examples
javascriptclassdraggableaddeventlistener

Javascript: Class Properties becoming undefined during Drag Events


I'm pretty new with classes in Javascript and I've been stuck for a few days trying to understand why my properties are becoming undefined in all of my methods...

I was trying to replicate this into a class, but was unsuccessful since my properties are undefined every-time a method is called.

I could not find a similar issue on stackoverflow, but please link me if there is one as I'm probably not searching the correct question.

<div class="row" style="height: 800px;">
    <div class="col" id="container">
        <div class="card" style="width: 300px; border-top: solid 20px black;" id="dragcard">
            <div class="card-body">
                <p>Text</p>
            </div>
        </div>
    </div>
</div>
class DragEvents {

    dragCard = document.querySelector("#dragcard");
    container = document.querySelector("#container");

    currentX = 0;
    currentY = 0;
    initialX;
    initialY;

    constructor() {

        this.container.addEventListener("mousedown", this.dragStart, false);
        this.container.addEventListener("mouseup", this.dragEnd, false);
        this.container.addEventListener("mousemove", this.drag, false);

        console.log(this.currentX);//works, but I need this to work in my methods.
    }

    drag() {
        console.log("Drag");//fires
        console.log(this.currentX);//undefined
        console.log(this.currentY);//undefined
        console.log(this.initialX);//undefined
        console.log(this.initialY);//undefined
    }

    dragStart() {
        console.log("DragStart");//fires
    }

    dragEnd() {
        console.log("DragEnd");//fires
    }

}//End Class
var DragEvent = new DragEvents();

Solution

  • This is a common issue in webapp development, especially when you're trying to use instance methods as event handlers.

    Normally, when you call something like

    instance.method(foo);
    

    The function method is called, with this pointing to instance and foo as the sole parameter. This is how most people expect this code to behave.

    However, instance.method (without the invocation) is just a reference to a function. If you did:

    const bar = instance.method;
    bar(foo);
    

    You would see different behavior. In this case, bar is called with this pointing to nothing and foo as the sole parameter. This is because the function is no longer bound to instance in the way it was when you called instance.method(foo);

    This is precisely what is happening when you call

    this.container.addEventListener("mousedown", this.dragStart, false);
    

    You pass in a reference to the function that this.dragStart points to, but the connection to your class is lost.

    There are a number of ways around this. They all do the same thing, effectively, which is to bind the event handlers to an instance of your class:

    Arrow function expressions You could use arrow function expressions to bind the value of this to your class:

    constructor() {
      this.container.addEventListener("mousedown", (e) => this.dragStart(e), false);
      this.container.addEventListener("mouseup", (e) => this.dragEnd(e), false);
      this.container.addEventListener("mousemove", (e) => this.drag(e), false);
    }
    

    bind method You could also use the bind method to explicitly bind this to the function reference

    constructor() {
      this.container.addEventListener("mousedown", this.dragStart.bind(this), false);
      this.container.addEventListener("mouseup", this.dragEnd.bind(this), false);
      this.container.addEventListener("mousemove", this.drag.bind(this), false);
    }
    

    ES6 method definition You can also change how you define your class methods so that the functions are bound to an instance of the class:

    class DragEvents {
    
        /* ... */
    
        drag = () => { /* ... */ }
    
        dragStart = () => { /* ... */ }
    
        dragEnd = () => { /* ... */ }
    
    }//End Class