Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 376
CodingCodger Avatar asked Dec 16 '14 19:12

CodingCodger


People also ask

How do you call a function after a specific delay?

To call a jQuery function after a certain delay, use the siteTimeout() method. Here, jQuery fadeOut() function is called after some seconds.

How do you call an existing function in JavaScript?

The call() method is a predefined JavaScript method. It can be used to invoke (call) a method with an owner object as an argument (parameter). With call() , an object can use a method belonging to another object.

How do you call a function After completion of another function?

trigger('function_a_complete'); } function b() { // second function code here } $(document). bind('function_a_complete', b); Using this method, function 'b' can only execute AFTER function 'a', as the trigger only exists when function a is finished executing.

How can call function again and again in jQuery?

Answer: Use the JavaScript setInterval() method You can use the JavaScript setInterval() method to execute a function repeatedly after a certain time period.


2 Answers

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.

like image 55
jfriend00 Avatar answered Oct 20 '22 01:10

jfriend00


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);
like image 25
Roamer-1888 Avatar answered Oct 19 '22 23:10

Roamer-1888