Event Handlers in ECMAScript 6

Back to my web application development (now I have a test environment up and running). I was looking at ECMAScript 6 code and specifically event handlers – what’s the best way to define an event handler. If you have dealt with Javascript event handlers in the past, you know you have to jump through hoops to preserve this across the event handler boundary.

A typical event handler may be like this:

        ApplicationMenu = function(id) {
            document.getElementById(id).onclick = function () {
                console.log("This = %o", this);
            };
            return {
                el: document.getElementById(id)
            }
        };

        ApplicationMenu.prototype = {
            onClick: function() {
                console.log("In onClick: this = %o", this);
            }
        };

        myAppMenu = new ApplicationMenu('clickme');

It’s a pretty newbie mistake – you rely on the this variable inside your onClick method but soon recognize that this is actually pointing to the HTMLElement that the event handler is bound to. I didn’t even call the onClick() method here because it simply isn’t valid. A better ECMAScript 5 solution would be this:

        ApplicationMenu = function (id) {
            var that = this;
            document.getElementById(id).onclick = function () {
                that.onClick();
            };
            return {
                el: document.getElementById(id)
            }
        };

        ApplicationMenu.prototype = {
            onClick: function() {
                console.log("In onClick: this = %o", this);
            }
        };

        myAppMenu = new ApplicationMenu('clickme');

Notice that we preserve the this variable (which gets written over constantly) into another variable that is in scope called that. Now the object is preserved and the onClick() method is called in the right context.

This is a problem even in ECMAScript 6. You can’t just go ahead and register the event handler, for instance:

class ApplicationMenu {
    constructor(activator) {
        this.el = document.createElement("div");
        this.el.className = "myClassName";
        this.el.style.display = "none";
        activator.addEventListener("click", this.onClick);
    }

    onClick(evt) {
        console.log("In onClick: this = %o", this);
        this.el.style.display = "";   // Ooops - this will break things
    }
}

You might expect that this is honored within the class. But the beauty of async programming bites you again. The this variable is the thing doing the call – not the thing that set up the call.

So how do you handle this case? You could just go ahead and do the same thing you used to do – ES6 is backwards compatible. That is a little hack-y though. ES6 Fat Arrow Functions to the rescue! Here is your canonical recipe for properly handling events inside of classes:


class MyClass {
    constructor(activator) {
        this.el = document.createElement("div");
        this.el.className = "myClassName";
        this.el.style.display = "none";
        // THIS IS THE IMPORTANT LINE
        activator.addEventListener("click", (e) => { this.onClick(e); });
    }

    onClick(evt) {
        console.info("In onClick: this = %o", this);
        this.el.style.display = "";
    }
}

The encapsulation afforded to us by the arrow function restores the context before calling the event handler. Don’t forget to store your activator in your class as a property if you need to access it within your event handler. It’s also available in the Event object your event handler is passed.