frenchie
frenchie

Reputation: 52047

jquery setting the order of handlers

I have a class of div MyClass I want to hook up to two handlers. This class is generated dynamically and this is what I'm doing:

$('#MainDiv').on({
 click: function () { Handler1($(this)); }
}, '.MyClass');

$('#SpecialChildDiv').on({
 click: function () { Handler2($(this)); }
}, '.MyClass');

How do I control which handler is called first? Handler2 does some additional work but it's not called on every MyClass because it's not in the SpecialChildDiv. I want Handler 2 to be called AFTER Handler1.

Thanks.

Upvotes: 0

Views: 62

Answers (2)

user1106925
user1106925

Reputation:

Because the SpecialChildDiv is presumably a descendant of MainDiv, it will be called first. This is because the event bubbles up from the target, and invokes handlers found along the way up in that order.

One possibility would be to make the call to Handler2 in the SpecialChildDiv handler asynchronous by using a setTimeout.

$('#MainDiv').on({
   click: function () { 
       Handler1($(this)); 
   }
}, '.MyClass');

$('#SpecialChildDiv').on({
   click: function () { 
       setTimeout($.proxy(function() {
           Handler2($(this));
       }, this), 0);
   }
}, '.MyClass');

So the handler itself will be invoked right away, but your Handler2 function won't run until the after the Handler1 has been called.

I've also used $.proxy to retain the correct this value in the function you pass to setTimeout.


A little off topic, but you can make your code cleaner if you change Handler1 and Handler2 so that this is a reference to the element, instead of passing it as an argument...

$('#MainDiv').on({
   click: Handler1
}, '.MyClass');

$('#SpecialChildDiv').on({
   click: function () { 
       setTimeout($.proxy(Handler2, this), 0);
   }
}, '.MyClass');

You'll just need to do $(this) inside your functions to wrap the element in a jQuery object. Though this may not be useful if there are other arguments to pass.


Based on your comment below, it seems that this technique will need to be applied to various descendant elements.

To DRY up the code, you can create a factory for your click handlers...

function async_handler_factory(the_handler) {
    return function() {
        setTimeout($.proxy(function() {
           the_handler($(this));
        }, this), 0);
    };
}

...then use it like this...

$('#MainDiv').on({
   click: Handler1
}, '.MyClass');

$('#SpecialChildDiv').on({
   click: async_handler_factory(Handler2)
}, '.MyClass');

$('#AnotherChildDiv').on({
   click: async_handler_factory(Handler3)
}, '.MyClass');

So it's the same principle. Just that you pass your HandlerN function to async_handler_factory, and it will create and return the click handler function, which does the same as my original code by invoking the HandlerN in a setTimeout.


Just to avoid confusion about $.proxy, here's an updated version that eliminates it.

function async_handler_factory(the_handler) {
    return function() {
        var self = this;
        setTimeout(function() {
           the_handler($(self));
        }, 0);
    };
}

This shows that it's actually the setTimeout that is making your solution work.


EDIT: Had a different name for async_handler_factory in different places. Fixed.

Upvotes: 1

Blazemonger
Blazemonger

Reputation: 92983

So don't call the second one on click -- call it after the first handler is done:

$('#MainDiv').on({
    click: function (e) { 
        Handler1($(this)); 
        if ($(e.target).attr('id')==="SpecialChildDiv") {
            specialChildClick();
        }
    }
}, '.MyClass');

specialChildClick = function () {
    Handler2($('#SpecialChildDiv')); 
}

Upvotes: 0

Related Questions