rancor1223
rancor1223

Reputation: 356

Execution order bahaviour

I'm writing a function that upon call should populate the page with tiles. Data on tiles are acquired from the remote DB (hence the AJAX request). I'm also using jQuery 3.0 in the code.

Here is the function:

function populateDashboard() {
    var tileBlueprint = '<div class="dashboard_tile">\
                    <div class="dashboard_tile_content">\
                        <table class="tile_table">\
                            <tr class="tile_title">\
                                <td>$title</td>\
                            </tr>\
                            <tr class="tile_data">\
                                <td>$value</td>\
                            </tr>\
                        </table>\
                    </div>\
                </div>';

$.ajax({
        url: 'http://' + AppVar.ServerUrl + '/restapi/Dashboard_GetData',
        type: 'POST',
        data: JSON.stringify({
            'SessionId': AppVar.SessionId
        }),
        dataType: 'json',
        contentType: "application/json",
        success: function (data) {
            if (data.ResultCode === '0') {
                //current tiles get wiped
                $('.dashboard_tile').fadeOut(100, function () {
                    $(".tile_handler").empty();
                    console.log("1 - " + Date.now())
                });

                //new tiles are parsed and data is injected into the string which represents the tile
                //tiles are saved into an array
                var json = $.parseJSON(data.TileData);
                var tileArr = [];
                $.each(json.tiles, function (index, element) {
                    tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
                    tile = tile.replace("$value", element.value);
                    tileArr[index] = tile;
                    console.log("2 - " + Date.now())
                });

                //now I loop trough the previously created array to populate the page
                $.each(tileArr, function (index, element) {
                    setTimeout(function () {
                        $(element).hide().appendTo(".tile_handler").fadeIn(1000);
                    }, 1000 * index); //delay made longer to see the effect better
                    console.log("3 - " + Date.now())
                });
            } else {
                ons.notification.alert($.i18n('error-retriving_data_problem'));
                return;
            }
        },
        error: function (request, error) {
            ons.notification.alert($.i18n('error-conn_error'));
            return;
        }
    });
}

I don't think the HTML where this is getting injected is relevat as that part is working fine.

The problem is that the fadeout and both each loops that get called upon success, get callout out of order. I tried to log the time at which each get executed and this is what I get:

//first run
2 - 1469707068268 (6 times)
3 - 1469707068269 (6 times)
//second run
2 - 1469707181179 (2 times)
2 - 1469707181180 (3 times)
2 - 1469707181181
3 - 1469707181181
3 - 1469707181182 (4 times)
3 - 1469707181183
1 - 1469707181283
1 - 1469707181284 (2 times)
1 - 1469707181285 (2 times)
1 - 1469707181286

I'm displaying 6 tiles, so comments 2 and 3 should fire 6 times and 1 only once.

Another problem is that the first time it displays 6 tiles, but second (and onwards), it only displays 5 tiles (first is missing).

Anyone can help me explain what is going on and how can I avoid such bahaviour?

Thank you.

Upvotes: 1

Views: 58

Answers (2)

Peter Eliasson
Peter Eliasson

Reputation: 71

Why doesn't 1 execute first and why is 1 executed 6 times?

From the documentation for .fadeOut, the second parameter is "A function to call once the animation is complete, called once per matched element".

So in this case the function will be called after ~100ms (the delay you provide as the first parameter) and will be called six times (once for each matched element).

If 1 is executed last, why doesn't it delete all the tiles created before?

As seen above, 1 is run after 100ms. However, the actual nodes are added after 1000 * index ms:

setTimeout(function () {
    $(element).hide().appendTo(".tile_handler").fadeIn(1000);
}, 1000 * index);

So for all but the first node the code actually appending the node is run after 1. However, for the first node (note: index of 0 => 1000 * 0 = 0ms delay), the appendTo code is run directly which means that it will in fact be removed when the .empty() is called after 100ms, which means you will only see 5 of the 6 nodes.

The solution to these problems is to somehow "synchronize" the code so that it runs in the way you expect it to. This is generally what callbacks are used for, you put the code you want to run after something is completed into the callback function. In this case, one solution could be to move the "adding" code into the fadeOut callback:

$('.dashboard_tile').fadeOut(100).promise().done(function () {
    $(".tile_handler").empty();
    var json = $.parseJSON(data.TileData);
    var tileArr = [];
    $.each(json.tiles, function (index, element) {
        tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
        tile = tile.replace("$value", element.value);
        tileArr[index] = tile;
    });
    // ...
});

Notice the usage of .promise.done, which gives us a single callback once all elements have finished animating instead of one for each element.

Upvotes: 3

MonkeyZeus
MonkeyZeus

Reputation: 20747

I see multiple issues with your code so here is what I can recommend:


data: JSON.stringify({
    'SessionId': AppVar.SessionId
}),

should just be

data: {'SessionId': AppVar.SessionId},

because jQuery's AJAX function will convert it for you.


Try console.log(data.TileData);; if you are already receiving a JS object/array then there is ZERO reason to call var json = $.parseJSON(data.TileData); so you should remove that.


Instead of

$.each(json.tiles, function (index, element) {`

use

$.each(data.TileData.tiles, function (index, element) {

Now for the final issue, fadeOut() and fadeIn() getting called out of order.

Try this:

// Make sure the fadeOut() finishes before calling more stuff!!!

//current tiles get wiped
$('.dashboard_tile').fadeOut(100, function () {
    $(".tile_handler").empty();
    console.log("1 - " + Date.now())

    //new tiles are parsed and data is injected into the string which represents the tile
    //tiles are saved into an array
    var tileArr = [];
    $.each(data.TileData.tiles, function (index, element) {
        tile = tileBlueprint.replace("$title", $.i18n("dashboard-" + element.title));
        tile = tile.replace("$value", element.value);
        tileArr[index] = tile;
        console.log("2 - " + Date.now())
    });

    //now I loop trough the previously created array to populate the page
    $.each(tileArr, function (index, element) {
        setTimeout(function () {
            $(element).hide().appendTo(".tile_handler").fadeIn(1000);
        }, 1000 * index); //delay made longer to see the effect better
        console.log("3 - " + Date.now())
    });
});

Upvotes: 1

Related Questions