jQuery event handlers always execute in order they were bound – any way around this? [duplicate]

Updated Answer

jQuery changed the location of where events are stored in 1.8. Now you know why it is such a bad idea to mess around with internal APIs 🙂

The new internal API to access to events for a DOM object is available through the global jQuery object, and not tied to each instance, and it takes a DOM element as the first parameter, and a key (“events” for us) as the second parameter.

jQuery._data(<DOM element>, "events");

So here’s the modified code for jQuery 1.8.

// [name] is the name of the event "click", "mouseover", .. 
// same as you'd pass it to bind()
// [fn] is the handler function
$.fn.bindFirst = function(name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.on(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    this.each(function() {
        var handlers = $._data(this, 'events')[name.split('.')[0]];
        // take out the handler we just inserted from the end
        var handler = handlers.pop();
        // move it at the beginning
        handlers.splice(0, 0, handler);
    });
};

And here’s a playground.


Original Answer

As @Sean has discovered, jQuery exposes all event handlers through an element’s data interface. Specifically element.data('events'). Using this you could always write a simple plugin whereby you could insert any event handler at a specific position.

Here’s a simple plugin that does just that to insert a handler at the beginning of the list. You can easily extend this to insert an item at any given position. It’s just array manipulation. But since I haven’t seen jQuery’s source and don’t want to miss out on any jQuery magic from happening, I normally add the handler using bind first, and then reshuffle the array.

// [name] is the name of the event "click", "mouseover", .. 
// same as you'd pass it to bind()
// [fn] is the handler function
$.fn.bindFirst = function(name, fn) {
    // bind as you normally would
    // don't want to miss out on any jQuery magic
    this.bind(name, fn);

    // Thanks to a comment by @Martin, adding support for
    // namespaced events too.
    var handlers = this.data('events')[name.split('.')[0]];
    // take out the handler we just inserted from the end
    var handler = handlers.pop();
    // move it at the beginning
    handlers.splice(0, 0, handler);
};

So for example, for this markup it would work as (example here):

<div id="me">..</div>

$("#me").click(function() { alert("1"); });
$("#me").click(function() { alert("2"); });    
$("#me").bindFirst('click', function() { alert("3"); });

$("#me").click(); // alerts - 3, then 1, then 2

However, since .data('events') is not part of their public API as far as I know, an update to jQuery could break your code if the underlying representation of attached events changes from an array to something else, for example.

Disclaimer: Since anything is possible :), here’s your solution, but I would still err on the side of refactoring your existing code, as just trying to remember the order in which these items were attached can soon get out of hand as you keep adding more and more of these ordered events.

Leave a Comment