Multiple JS event handlers on single element

jQuery makes this easy.

$(document).on('click', '.someclass', function() {
  doStuff();
});

$(document).on('click', '.someclass', function() {
  doMoreStuff();
});

Handlers then both will fire on click. jQuery keeps a queue of handers for you. And handles document clicks that match a selector of your choice so that they can be triggered no matter when your buttons are created.


In JS, you don't really have control over what order the event handlers are called, but with careful delegation and well-placed listeners, it is possible.

Delegation is one of the most powerful features of the event model. As you may or may not know: in JS, an event is handed to the top of the dom, from where it propagates down to the element onto which the event should be applied. It stands to reason, therefore, that an event listener attached to the global object will call its handler prior to a listener that has been attached to the element itself.

window.addEventListener('click',function(e)
{
    e = e || window.event;
    var target = e.target || e.srcElement;
    console.log('window noticed you clicked something');
    console.log(target);//<-- this is the element that was clicked
}, false);//<-- we'll get to the false in a minute

It's important to note we actually have access to the event object in the handlers. In this case, we left the event object untouched, so it'll just continue to propagate down to the target, on its way down, it might meet with something like this:

document.getElementById('container').addEventListener('click', function(e)
{
    e = e || window.event;
    var target = e.target || e.srcElement;
    if (target.tagName.toLowerCase() !== 'a' || target.className.match(/\bclickable\b/))
    {
        return e;//<return the event, unharmed
    }
    e.returnValue = false;
    if (e.preventDefault)
    {
        e.preventDefault();
    }
}, false);

Now, this handler will be called after the listener at the window level calls its helper. This time, the event is changed if the clicked element didn't have the clickable class, or the element is a link. The event is canceled, but it lives on, still. The event is still free to propagate further down the dom, so we might encounter something like:

document.getElmentById('form3').addEventListener('click',function(e)
{
     e = e || window.event;
     if (e.returnValue === false || e.isDefaultPrevented)
     {//this event has been changed already
         //do stuff, like validation or something, then you could:
         e.cancelBubble = true;
         if (e.stopPropagation)
         {
             e.stopPropagation();
         }
     }
}, false);

Here, by calling stopPropagation, the event is killed off. It can't propagate further down the dom to its target unless the event was already altered. If not, the event object travels further down the DOM, as if nothing happened.

Once it reaches its target node, the event enters its second phase: the bubble phase. Instead of propagating down into the deeps of the DOM, it climbs back up, to the top level (all the way to the global object, where it was dispatched... from whence it came and all that).

In the bubble phase, all the same rules apply as in the propagation phase, only the other way around. The event object will encounter the elements that are closest to the target element first, and the global object last.

There's a lot of handy, and clear diagrams for this here. I can't put it any better than good 'ol quirksmode, so I suggest you read what they have to say there.

Bottom line: when dealing with 2 event listeners, attach them both on a different level to sort-of queue them the way you like.

If you want to guarantee both are called, only stop the event from propagating in that handler that will be called last.

When you've got two listeners, attached to the same element/object for the same event, I've never come across a situation where the listener that was attached first, wasn't also called first.

That's it, I'm off to bed, hoping I made sense