How to order events bound with jQuery

How to order events bound with jQuery

Lets say I have a web app which has a page that may contain 4 script blocks – the script I write may be found in one of those blocks, but I do not know which one, that is handled by the controller.
I bind some onclick events to a button, but I find that they sometimes execute in an order I did not expect.
Is there a way to ensure order, or how have you handled this problem in the past?

Solutions/Answers:

Solution 1:

I had been trying for ages to generalize this kind of process, but in my case I was only concerned with the order of first event listener in the chain.

If it’s of any use, here is my jQuery plugin that binds an event listener that is always triggered before any others:

** UPDATED inline with jQuery changes (thanks Toskan) **

(function($) {
    $.fn.bindFirst = function(/*String*/ eventType, /*[Object])*/ eventData, /*Function*/ handler) {
        var indexOfDot = eventType.indexOf(".");
        var eventNameSpace = indexOfDot > 0 ? eventType.substring(indexOfDot) : "";

        eventType = indexOfDot > 0 ? eventType.substring(0, indexOfDot) : eventType;
        handler = handler == undefined ? eventData : handler;
        eventData = typeof eventData == "function" ? {} : eventData;

        return this.each(function() {
            var $this = $(this);
            var currentAttrListener = this["on" + eventType];

            if (currentAttrListener) {
                $this.bind(eventType, function(e) {
                    return currentAttrListener(e.originalEvent); 
                });

                this["on" + eventType] = null;
            }

            $this.bind(eventType + eventNameSpace, eventData, handler);

            var allEvents = $this.data("events") || $._data($this[0], "events");
            var typeEvents = allEvents[eventType];
            var newEvent = typeEvents.pop();
            typeEvents.unshift(newEvent);
        });
    };
})(jQuery);

Things to note:

  • This hasn’t been fully tested.
  • It relies on the internals of the jQuery framework not changing (only tested with 1.5.2).
  • It will not necessarily get triggered before event listeners that are bound in any way other than as an attribute of the source element or using jQuery bind() and other associated functions.
Related:  Window.Open with PDF stream instead of PDF location

Solution 2:

If order is important you can create your own events and bind callbacks to fire when those events are triggered by other callbacks.

$('#mydiv').click(function(e) {
    // maniplate #mydiv ...
    $('#mydiv').trigger('mydiv-manipulated');
});

$('#mydiv').bind('mydiv-manipulated', function(e) {
    // do more stuff now that #mydiv has been manipulated
    return;
});

Something like that at least.

Solution 3:

Dowski’s method is good if all of your callbacks are always going to be present and you are happy with them being dependant on each other.

If you want the callbacks to be independent of each other, though, you could be to take advantage of bubbling and attach subsequent events as delegates to parent elements. The handlers on a parent elements will be triggered after the handlers on the element, continuing right up to the document. This is quite good as you can use event.stopPropagation(), event.preventDefault(), etc to skip handlers and cancel or un-cancel the action.

$( '#mybutton' ).click( function(e) { 
    // Do stuff first
} );

$( '#mybutton' ).click( function(e) { 
    // Do other stuff first
} );

$( document ).delegate( '#mybutton', 'click', function(e) {
    // Do stuff last
} );

Or, if you don’t like this, you could use Nick Leaches bindLast plugin to force an event to be bound last: https://github.com/nickyleach/jQuery.bindLast.

Related:  Verify if my node.js instance is dev or production

Or, if you are using jQuery 1.5, you could also potentially do something clever with the new Deferred object.

Solution 4:

The order the bound callbacks are called in is managed by each jQuery object’s event data. There aren’t any functions (that I know of) that allow you to view and manipulate that data directly, you can only use bind() and unbind() (or any of the equivalent helper functions).

Dowski’s method is best, you should modify the various bound callbacks to bind to an ordered sequence of custom events, with the “first” callback bound to the “real” event. That way, no matter in what order they are bound, the sequence will execute in the right way.

The only alternative I can see is something you really, really don’t want to contemplate: if you know the binding syntax of the functions may have been bound before you, attempt to un-bind all of those functions and then re-bind them in the proper order yourself. That’s just asking for trouble, because now you have duplicated code.

It would be cool if jQuery allowed you to simply change the order of the bound events in an object’s event data, but without writing some code to hook into the jQuery core that doesn’t seem possible. And there are probably implications of allowing this that I haven’t thought of, so maybe it’s an intentional omission.

Related:  JavaScript Event State Machine

Solution 5:

Please note that in the jQuery universe this must be implemented differently as of version 1.8. The following release note is from the jQuery blog:

.data(“events”): jQuery stores its event-related data in a data object
named (wait for it) events on each element. This is an internal data
structure so in 1.8 this will be removed from the user data name space
so it won’t conflict with items of the same name. jQuery’s event data
can still be accessed via jQuery._data(element, “events”)

We do have complete control of the order in which the handlers will execute in the jQuery universe. Ricoo points this out above. Doesn’t look like his answer earned him a lot of love, but this technique is very handy. Consider, for example, any time you need to execute your own handler prior to some handler in a library widget, or you need to have the power to cancel the call to the widget’s handler conditionally:

$("button").click(function(e){
    if(bSomeConditional)
       e.stopImmediatePropagation();//Don't execute the widget's handler
}).each(function () {
    var aClickListeners = $._data(this, "events").click;
    aClickListeners.reverse();
});

Solution 6:

function bindFirst(owner, event, handler) {
    owner.unbind(event, handler);
    owner.bind(event, handler);

    var events = owner.data('events')[event];
    events.unshift(events.pop());

    owner.data('events')[event] = events;
}