How can I use when() with multiple deferred to make effects run nonconcurrently?

69 views Asked by At

I am writing some simple UI code to rearrange a layout when a button is pressed.

I have a searchbar with three form fields, a "search" button, some text and a logo. On pressing "search," the button, logo and text fade out, the bar with text fields slides to the top of the page using jquery.animate(), the logo and search button are given different CSS to re-position them, and then fade back in at new locations.

I am trying to use deferred.done() using this JQuery documentation

I started with the below:

var fades = function () {
    $("#centerSearchText").fadeOut();
    $("#headerImage").fadeOut();
    $("#searchButton").fadeOut();
}

$.when( fades() ).done(function () {
    var positionUpdate = function() { 
        $("#headerImage").css({ "left": "12px", "margin-left": "12px", "margin-top": "12px", "float": "left" });
        $("#searchButton").appendTo("#search_input_table tr:first")
        $("#header").animate({
            top: "0px",
            marginTop: "0px",
        }, 500);
    }

    $.when(cssUpdate()).done(function () {
        $("#headerImage").fadeIn();
        $("#searchButton").fadeIn();
    });

});

...which did not work because every function was running concurrently. I realized I was not properly following the example to return a deferred object like in the linked example as below:

var effect = function() {
    return $( "div" ).fadeIn( 800 ).delay( 1200 ).fadeOut();
};

However I need to return when three fadeOuts() are done, rather than just one. So I updated my code to the below:

var fades = function () {
    return $.when($("#centerSearchText").fadeOut(), $("#headerImage").fadeOut(), $("#searchbtn").fadeOut()).done()
}

$.when( fades() ).done(function () {
    var cssUpdate = function() { 
        return $.when($("#headerImage").css({ "left": "12px", "margin-left": "12px", "margin-top": "12px", "float": "left" }),
                    $("#searchbtn").appendTo("#search_input_table tr:first"),
                    $("#header").animate({
                        top: "0px",
                        marginTop: "0px",
                    }, 500)).done(); 
    }
    $.when(cssUpdate()).done(function () {
        $("#headerImage").fadeIn();
        $("#searchbtn").fadeIn();
    });

});

...in which the UI elements are no longer running their effects concurrently, because no code is reached after the initial fadeOuts.

Can anyone enlighten me as to what I'm doing wrong here? I'm sure I'm misunderstanding the use of when() and done() somewhere, but I haven't been able to find great documentation for use of multiple deferred objects to compare with my code.

3

There are 3 answers

0
Terry On

Given that you are using an unmodified .fadeOut() animation for all three elements, a better way would be to push the promises into an array, and evaluate the array using $.when(), for example:

// Define vars
var fades = [],
    cssUpdates = [],
    positionUpdate = function() { 
        $("#headerImage").css({ "left": "12px", "margin-left": "12px", "margin-top": "12px", "float": "left" });
        $("#searchButton").appendTo("#search_input_table tr:first")
        $("#header").animate({
            top: "0px",
            marginTop: "0px",
        }, 500);
    },
    cssUpdate = function() { 
        return when($("#headerImage").css({ "left": "12px", "margin-left": "12px", "margin-top": "12px", "float": "left" }),
                    $("#searchbtn").appendTo("#search_input_table tr:first"),
                    $("#header").animate({
                        top: "0px",
                        marginTop: "0px",
                    }, 500)).done(); 
    }

// Fade out
$("#centerSearchText, #headerImage, #searchButton").fadeOut(function() {
    var d = new $.Deferred();
    fades.push(d.resolve());
});

// Listen to fadeOut completion
$.when.apply($, fades).then(function() {
    $.when(cssUpdate()).then(function() {
        $("#headerImage, #searchbtn").fadeIn();
    })
});
0
omikes On

Sometimes its easier to use a function call at the end of an animation:

$("#header").animate(
{
    top: "0px",
    marginTop: "0px",
}, 500, function()
{
    // call next animation function here, it will be called upon completion
});
0
artm On

You can use deferred:

var deferred1 = $.Deferred();
var deferred2 = $.Deferred();
var deferred3 = $.Deferred();

$("#centerSearchText").fadeOut(1000, function() { deferred1.resolve(); } );
$("#headerImage").fadeOut(1000, function() { deferred2.resolve(); } );
$("#searchButton").fadeOut(1000, function() { deferred3.resolve(); } );


$.when(deferred1, deferred2, deferred3).done(function() {

    return when($("#headerImage").css({ "left": "12px", "margin-left": "12px", "margin-top": "12px", "float": "left" }),
                $("#searchbtn").appendTo("#search_input_table tr:first"),
                $("#header").animate({
                    top: "0px",
                    marginTop: "0px",
                }, 500)).done(); 
}
$.when(cssUpdate()).done(function () {
    $("#headerImage").fadeIn();
    $("#searchbtn").fadeIn();

});

http://api.jquery.com/category/deferred-object/