简体   繁体   中英

Javascript/JQuery - how to call a function on completion of previous function

I'm using the following Javascript functions to display a gallery of pictures.

function disp_pics(currObj,table){
    if(currObj != "none"){
        $("div .checkout2").removeClass("checkout2").addClass("checkout");
        $(currObj).closest("div").removeClass("checkout").addClass("checkout2");
    }

    function getData(table){
        return $.ajax({
            url: "newphoto_gallery_display.php",
            type: "GET",
            data: {
                table: table
            },
            dataType: "html"
        });
    }

    function display_result(data){
        var dfd = new $.Deferred();
        dfd.done(equalise);
        $("#ajaxoutput").html(data);
        setTimeout(function() {
            dfd.resolve();
        }, 1000);
    }

    function equalise(){
        var highestBox = 0;
        $('.photodisplay').each(function(){
            if($(this).height() > highestBox)  {
                highestBox = $(this).height(); 
            }
        });  
        $('.photodisplay').height(highestBox);
    }

    var promise=getData(table);
    promise.success(function (data) {
        promise.done(display_result(data));
    });
};

The function getData fetches the picture data from a database. The function display_result then outputs that data to the div id "ajaxoutput". Pictures are displayed along with relevant data in boxes (HTML tables with a border). The function equalise is then called to make all the boxes of equal height.

Without the time delay in the display_result the equalise function is called before all the pictures and data have been displayed and therefore messes up the display. Is there a way to do away with the time delay and only call equalise function when display_result has finished outputting all the data to the ajaxoutput div?

I've tried all sorts of Deferred's and $.when.......then.... combinations, but haven't managed to achieve the desired result without delaying the script, which isn't ideal. Any suggestions would be welcome.

If the issue is that you need to know when all the images the HTML that you applied to #ajaxoutput have been loaded (and thus their size is known to the browser), then you can do that like this by monitoring the onload event for each of the images you added to the HTML:

function display_result(data){
    var dfd = new $.Deferred();
    var imgWaiting = 0;
    $("#ajaxoutput").html(data).find("img").each(function() {
        if (!this.complete) {
            // img is still loading, install a load handler so we know when it's done
            ++imgWaiting;
            this.onload = this.onerror = function() {
               --imgWaiting;
               if (imgWaiting === 0) {
                   dfd.resolve();
               }
            }
        }
    });
    if (imgWaiting === 0) {
         dfd.resolve();
    }
    return dfd.promise();
}

Then, you can do:

return getData(table).then(display_result).then(equalise);

Which is a classic design pattern with promises for serializing several async operations.

You need to get rid of the timeout delay as it's not guaranteed to be long enough for the images to have loaded (slow server, slow internet etc).

Unfortunately the "proper" solution you have been seeking is not the simplest, due mainly to the fact that you can only access the img nodes of interest after the statement $("#ajaxoutput").html(data) puts them in place. In the statement following that one, it will be uncertain (due to browser behaviour) whether the images are already loaded (from cache) or not. Simply attaching an "onload" handler would therefore be unreliable.

A workaround is to create a bunch of off-screen img nodes that mirror those in "#ajaxoutput" and to "promisify" them in such a way that their 'src' properties are set after attaching "onload" (and "onerror") handlers.

The code is not simple to follow so I've done my best to provide explanatory comments.

function display_result(data) {
    //First a function that creates a promisified <img> node, and returns its promise.
    //<img> nodes created here are not appended to the DOM.
    function imgPromise(index, imgNode) {
        return $.Deferred(function(dfrd) {
            //Note the order here - the onload/onerror handler is attached before the src attribute is set.
            //Note also that both onload/onerror cause the deferred to be resolved, as you don't want any failures to prevent calling `equalise()`.
            $("<img/>").on('load error', dfrd.resolve).attr('src', imgNode.src);
        }).promise();
    }

    //A lot happens in the next line ...
    // * "#ajaxoutput" is given some HTML
    // * any freshly created image nodes in "#ajaxoutput" are discovered
    // * `imgPromise()` is called for each img node in turn and the returned promises are stuffed into an array
    // * the array is assigned to local var `promises`.
    var promises = $("#ajaxoutput").html(data).find("img").map(imgPromise);

    //Finally, `$.when()` returns a promise that is resolved when all `promises` have settled.
    //This "summary promise" is returned.
    return $.when.apply(null, promises);
}

Without the comments, you will see that this is actually very concise and hopefully a little less scary.

function display_result(data) {
    function imgPromise(index, imgNode) {
        return $.Deferred(function(dfrd) {
            $("<img/>").on('load error', dfrd.resolve).attr('src', imgNode.src);
        }).promise();
    }
    var promises = $("#ajaxoutput").html(data).find("img").map(imgPromise);
    return $.when.apply(null, promises);
}

Call as follows :

getData(table).then(display_result).then(equalise);

Can you run them synchronously?

function display_result(data){
    $("#ajaxoutput").html(data);
    equalise();
}

I'm pretty sure the .html function is a synchronous call, so your resizing will happen after this.

Edited: What does the data look like coming from your ajax call? Is it just a bunch of img tags? If so, you'd probably be better off loading the image in ajax and encoding it into the html yourself.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM