Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

jQuery .when().done() not working

I'd like to start by saying I'm new to jQuery and I suspect I'm just doing something stupid, so hopefully this will be very simple for someone.

I'm trying to add a sliding mobile sub-menu to my website. I want an accordian effect whereby if I click one parent link, it's child sub-menu opens and all other sub-menus close. The problem is timing - the child sub-menu opens and then is closed again by the resetting of all sub-menus. I presume the answer is to use deferreds but everything I've tried has failed. This is the (currently not working) code:

function ResetMenu(){
    jQuery(".mobile-menu").find(".sub-menu").slideUp(100);
    jQuery(".mobile-menu").find(".menu-item-has-child").removeClass("open");
};

function OpenSubmenu(){
    jQuery(this).next("ul").slideDown(100);
    jQuery(this).parent().addClass("open");
};

jQuery("li.menu-item-has-children > a").click(function(){

    if(jQuery(this).parent().hasClass("open")){
        jQuery(".mobile-menu").find(".sub-menu").slideUp(100);
        jQuery(this).parent().removeClass("open");
    } else {
        jQuery.when(ResetMenu()).done(OpenSubmenu());
    }
    return false;
});

Any help would be greatly appreciated. Thank you!

Ronel

like image 445
Ronelz Avatar asked Feb 18 '15 03:02

Ronelz


2 Answers

This is a common mistake in how to use jQuery.when().

jQuery.when() requires promises as arguments. It does not have magical powers to know when functions you pass it are somehow done. Those functions MUST return promises that are resolved or rejected when the underlying code is done and you can then pass those promises to jQuery.when().

Your ResetMenu() function doesn't return anything so therefore, your jQuery.when() doesn't wait for anything. It executes the .then() handler immediately (which looks like that is not what you want).

So, in this line:

jQuery.when(ResetMenu()).done(OpenSubmenu());

ResetMenu() MUST return a promise for jQuery.when() to know when it is done.

You could fix ResetMenu() to work that way by doing this:

function ResetMenu(){
    return jQuery(".mobile-menu").find(".sub-menu").slideUp(100).promise().then(function() {
        // remove this class when the animation has completed
        jQuery(".mobile-menu").find(".menu-item-has-child").removeClass("open");
    });
};

And, then further, you need to change how you pass a function to .done() to this which both makes it just a function reference that can be executed LATER and binds a appropriate this value to it:

jQuery.when(ResetMenu()).done(OpenSubmenu.bind(this));

Note, the .bind(this) assumes that this is the appropriate value. You can pass whatever value is the correct value there and that will become the this value inside of OpenSubmenu() when it is executed.

like image 68
jfriend00 Avatar answered Sep 27 '22 23:09

jfriend00


When you pass a non promise object to $.when() the callbacks passed to done() is invoked immediately, in your case when ResetMenu is not returning anything the OpenSubmenu is called immediately, there is another problem also - you should not invoke OpenSubmenu directly(by adding ()), you need to pass a function reference to done()

function ResetMenu() {
    jQuery(".mobile-menu").find(".menu-item-has-child").removeClass("open");
    return jQuery(".mobile-menu").find(".sub-menu").slideUp(100).promise();

};

function OpenSubmenu() {
    jQuery(this).next("ul").slideDown(100);
    jQuery(this).parent().addClass("open");
};

jQuery("li.menu-item-has-children > a").click(function () {

    if (jQuery(this).parent().hasClass("open")) {
        jQuery(".mobile-menu").find(".sub-menu").slideUp(100);
        jQuery(this).parent().removeClass("open");
    } else {
        jQuery.when(ResetMenu()).done(OpenSubmenu);
    }
    return false;
});
like image 33
Arun P Johny Avatar answered Sep 27 '22 23:09

Arun P Johny