addEventListener, handleEvent and passing objects

Here’s a super awesome trick I had no idea about until someone pointed out you could do this. addEventListener can take an object as a second argument that will look for a method called handleEvent and call it! No need for binding “this” so it will pass around the context correctly, the context is the object you’ve just set as the event listener callback.

 
var obj = {
    handleEvent: function() {
        alert(this.dude);
    },
    dude: "holla"
};

element.addEventListener("click", obj, false);

Why is this awesome?

We can do things like this:

var obj  =  {
    init: function() {
        document.getElementById("btn").addEventListener("click", this, false);
        document.getElementById("btn").addEventListener("touchstart", this, false);
    },
    handleEvent: function(e) {
        switch(e.type) {
            case "click":
                this.button();
                break;
            case "touchstart":
                this.button();
                break;
        }
    },
    dude: "holla",
    button: function() {
        alert(this.dude);
    }
};

obj.init();

As you can see our object has an init() method which binds all the events and just passes in “this” as the callback, handleEvent then delegates off to whatever method it needs based on the event being triggered, rad.

But wait there’s more

Another awesome thing we can do is change the buttons behaviour without having to remove and re-attach the event handler. The second button in the demo changes what the first button does by redefining the handleEvent method.

changeHandleEvent: function(evt) {
    this._handleEvent = this.handleEvent;
    // Change the the handleEvent method without needing to remove 
    // and re-attach the event(s)
    this.handleEvent = function(e) {
        var t = evt.target;
        
        if (t.id === "btn") {
            // Check the button being clicked and do new stuff
        } else if(t.id === "btn3") {
            this.revertHandleEvent();
        }
    }
}

Makes for some pretty powerful stuff. The third button re-instates the first buttons behaviour back to its original form.

This works in nearly all browsers that support addEventListener, Blackberry 6 browser being one that doesn’t. Blackberry OS7 has fixed this bug.

Polyfill it

IE9 was the first to support addEventListener + handleEvent in the IE family, so for the older ones we can polyfill in support for <IE8 and other non-supporting browsers (BBOS6).

// fn arg can be an object or a function, thanks to handleEvent
function on(el, evt, fn, bubble) {
    if("addEventListener" in el) {
        // BBOS6 doesn't support handleEvent, catch and polyfill
        try {
            el.addEventListener(evt, fn, bubble);
        } catch(e) {
            if(typeof fn == "object" && fn.handleEvent) {
                el.addEventListener(evt, function(e){
                    // Bind fn as this and set first arg as event object
                    fn.handleEvent.call(fn,e);
                }, bubble);
            } else {
                throw e;
            }
        }
    } else if("attachEvent" in el) {
        // check if the callback is an object and contains handleEvent
        if(typeof fn == "object" && fn.handleEvent) {
            el.attachEvent("on" + evt, function(){
                // Bind fn as this
                fn.handleEvent.call(fn);
            });      
        } else {
            el.attachEvent("on" + evt, fn);
        }
    }
}

For oldIE it checks if the callback is an object and if it contains handleEvent. Using call we can change the “this” context to the object passed.

For BBOS6 we wrap a try catch around addEventListener and if the attaching throws, check the callback is an object and has the handleEvent method otherwise don’t swallow the error.

We handle all this by wrapping up the event attaching into a nice method which handles the process for us.

Resources

I’m certainly not the first to document this, see ajaxian article with some great examples.

Even HTML5 boilerplate mobile uses this technique for their fast touch event handling.

Short URL: http://cssn.in/ja/036

 

Post filed under: javascript.

Skip to comment form.

  1. Ionut says:

    Hi,
    I think if u bind a function as the event handler you can also easily change the action based on the event target. I don’t see it as an advantage of using object passing

  2. Dmitry Pashkevich says:

    This is really an awesome finding! Thanks for sharing!

    @Ionut I think we have two big advantages here:

    1) No need to add/use framework methods for binding an event handler with a specific “this” scope. (I assume you employ the OOP paradigm in JS)

    2) The handler function doesn’t have to exist at the moment of binding and can be replaced withing the object.

    There are probably less real world cases where you can benefit from 2) but 1) is absolutely kicking ass! Simpler and faster. I would still use the event binding framework from popular js libraries if I need advanced functionality but if I’m targeting a specific subset of browsers and performance is an important issue then I’d rather keep my framework as minimal as possible and take advantage of the natively available stuff.

    Cheers!

  3. Ionut says:

    Hi,
    @1 usually this is used inside the handler function to refer to the target element, i think that’s the most useful
    and @2 you can define the function after you attach the handler

    BUT this is a great find anyway and as u say when not using a Js framework i think it’s an interesting pattern to use

  4. Dmitry Pashkevich says:

    @Ionut, what do you mean “you can define the function after you attach the handler”?

  5. Thanks for sharing, Ryan.
    You may want to update the link to H5BP mobile. The new one is https://github.com/h5bp/mobile-boilerplate/blob/master/js/mylibs/helper.js#L70

  6. Ryan Seddon says:

    Updated link, thanks.

  7. dindog says:

    Hey, thanks. I just notice this hadleEvent way, then google you blog, nice and easy reading.

  8. Torsten says:

    @Ionut Sure you can bind the function before it is defined in _the same scope_. However, once the code gets more modular this won’t work anymore.

    Lets assume the following scenario:
    Say you have an object that builds the UI. Within a single function you want to build the dom and bind the events to have only one loop through all the elements. What you know by then is only the controller which will be handling events, you can only decide what to do in the event handler when all the UI elements are there.

    A solution is passing an object reference to the addEventListener function and later add the correct handler to that object.

    In my opinion this is a great advantage over the function based approach.

    Regards,

Leave a comment