0

I've been following this answer in an attempt to write good asynchronous js - but I can't quite figure out what's going wrong for me.

I'm using autocomplete to request a specific set of files from the server, but for some datasets these files may not exist (i.e, there may be one, or up to four files). If they do exist, I want to append .html to include them.

The images don't load unless I add

async: false

to the .ajax call, which makes the callback redundant.

for (var i = 1; i < 5; i++) {
  (function (counter) {
    // define function with the callback argument
    function ajaxTest(callback) {
      $.ajax({
        type: "GET",
        url: counter + "_1.jpg",
        success: function (result) {
          callback(counter);
        }
      });
    }
    // call the function
    ajaxTest(function (num) {
      var image_temp = '<div class="thumbnail"> <img class="img-thumbnail" src="'+ num + '_1.jpg" /> Image' + num + '</div>';
      console.log(image_temp); //check readout
      image_html += image_temp;
    });
  })(i);
}

Then image_html contains html for only those images that exist.

$('#outputcontent').html(outer_html + image_html + outer_html_end);

Can anyone explain to my misunderstanding here please? Why isn't image_html being populated?

EDIT: 'full' code below. I use jquery autocomplete to pull from the stellar_comp array, then for those with properties that exist (some are blank), html is generated. Within that is the ajax call.

$(function () {
 var stellar_comp = [
     {
         value:'X0005-28',
         data: {
             set0: {
                 aperture:'X2105-28',
                 secat:'X025-18',
                 set:'1',
                 run:'r06',
                 continuumFilter:'J',
                 narrowBandFilter:'638/28',
                 numberOfSources:'1',
                 priorityCode:'1'
                    etc... etc ...
        },      
   },
 ];


 $('#autocomplete').autocomplete({
    lookup: stellar_comp,
    onSelect: function (suggestion) {


    var path_Value = 'data/' + suggestion.value + '/';
    var outer_html = '<h1>' + suggestion.value + ' </h1> <div class="container">';
    var outer_html_end = '</div>';


    for (var category in suggestion.data) {
        if (suggestion.data.hasOwnProperty(category)) {
            if (suggestion.data[category].aperture) {

                summary_table_contents = '<tr> <td>' + suggestion.data[category].set + '</td> <td>' + suggestion.data[category].run + '</td> <td>' + suggestion.data[category].continuumFilter + '</td> <td>' + suggestion.data[category].narrowBandFilter + '</td> <td>' + suggestion.data[category].numberOfSources + '</td> <td>' + suggestion.data[category].priorityCode + '</td></tr> ';

                summary_table += summary_table_contents;

                var aperturePlot = suggestion.data[category].aperture + '_' + suggestion.data[category].run;
                var seCATPlot = suggestion.data[category].secat + '_Rsub_ss_' + suggestion.data[category].run;
                var aperture_match = suggestion.data[category].aperture_match;


                cog = path_Plots + aperturePlot + '_cog';
                sbprof = path_Plots + aperturePlot + '_sbprof';
                thumb_cog = '';
                thumb_cog_temp = '';
                thumb_sb = '';
                temp='';
                for (var i = 1; i < 5; i++) {
                    (function (counter) {
                        function some_function(callback) {
                            $.ajax({
                                type: "GET",
                                url: cog + counter + "_1.jpg",
                                async: false,
                                success: function (result) {
                                    callback(counter);
                                }
                            });
                        }

                        some_function(function (num) {

                            var thumb_cog_temp = '<div class="col-lg-3 col-sm-4 col-xs-6"> <a class="thumbnail" target="_blank" href="' + cog + num + '_1.jpg"> <img class="img-thumbnail" src="' + cog + num + '_4.jpg" /></a> <div class="caption"><h5>' + suggestion.value + ':S' + num + '</h5></div></div>';
                            var thumb_sb_temp = '<div class="col-lg-3 col-sm-4 col-xs-6"> <a class="thumbnail" target="_blank" href="' + sbprof + num + '_1.jpg"><img class="img-thumbnail" src="' + sbprof + num + '_4.jpg" /></a><div class="caption"><h5>' + suggestion.value + ':S' + num + ' </h5></div></div>';
                            console.log(num, counter);
                            thumb_cog += thumb_cog_temp;
                            thumb_sb += thumb_sb_temp;
                        });
                    })(i);
                }


                cog_sbprofile_row='<div class="row"><h3>C o G</h3> ' + thumb_cog + '</div><div class="row"><h3>Profiles</h3>  ' + thumb_sb + '</div>';
                console.log(cog_sbprofile_row);
                body_html += aperture_row;
                body_html += seCAT_row;
                body_html += aperture_match_row;
                body_html += pixel_map_row;
                body_html += skyprofile_row;
                body_html += cog_sbprofile_row;
                body_html += '<hr>';
            };
        };
    };
    top_html += summary_table + '</tbody> </table> </div></div> <hr>';

    $('#outputcontent').html(outer_html +  hipass_container + top_html + body_html +  outer_html_end);

     }
 });

});
Community
  • 1
  • 1
Liz
  • 1,421
  • 5
  • 21
  • 39
  • 1
    Do you execute "$('#outputcontent').html(outer_html + image_html + outer_html_end);" after all async task is completed? – Chickenrice Jul 07 '14 at 09:20
  • Ah, I don't know - how do I check for this? – Liz Jul 07 '14 at 09:32
  • As `Chickenrice` suggests, you have no code shown that would wait for all the async Ajax calls to finish before trying to update the HTML. You need to show the rest of your code to allow for a solution to be provided as your code will need to be reorganized. *P.S. You should never use `async: false`*. – iCollect.it Ltd Jul 07 '14 at 09:34
  • Q: Is the display order of the images important? Do they need to be sequential (or just in the order loaded)? – iCollect.it Ltd Jul 07 '14 at 09:37
  • Because the AJAX image loader will load picture asynchronously, you need to make sure that all pictures have been loaded properly before you append them to the container DOM. – Chickenrice Jul 07 '14 at 09:37
  • Have added more info. @Chickenrice how do I do that? Definitely sounds like that's what I've got to do - just unsure how to check. – Liz Jul 07 '14 at 09:53
  • Added a much simpler solution below using uniquely numbered/id placeholders. – iCollect.it Ltd Jul 07 '14 at 10:48
  • Just curious, but why use AJAX to load the images when you could just create an off-screen `Image` element ? – Alnitak Jul 07 '14 at 10:50
  • @Alnitak could you expand on this? I don't know how many images I'll be displaying until I make the ajax call - could be a single image, could be up to four. – Liz Jul 08 '14 at 02:13
  • @liz_ophiuchus the "normal" way to pre-load an image is to simply do `var img = new Image(); img.src = url` (with a `.onload` handler if required). Curious why you use XMLHTTPRequest with all the cross-origin restrictions that imposes. – Alnitak Jul 08 '14 at 07:10
  • simple answer - I don't know any better yet! So how would I do that client-side without first knowing which images existed? – Liz Jul 08 '14 at 07:55

2 Answers2

0

You have to do the appending after all ajax requests finish their jobs. First define this:

var after = function(reqs) {
    var d = $.Deferred(),
        cnt = reqs.length;

    if (!cnt) {
        d.resolve();
    }

    var limit = cnt;  // separate var just in case reqs are synchronous
                      // so it won't mess up the loop

    for (var i = 0; i < limit; i++) {
        reqs[i].always(function() {
            cnt--;
            if (!cnt) {
                d.resolve();
            }
        });
    }
    return d.promise();
};

You can use $.when instead of this but if one of the requests results in 404 it won't wait for others. Now:

var image_html = "",
    requests = [];

for (var i = 1; i < 5; i++) {
  (function (counter) {
    // define function with the callback argument
    function ajaxTest(callback) {
      return $.ajax({
        type: "GET",
        url: counter + "_1.jpg",
        success: function (result) {
          callback(counter);
        }
      });
    }
    // call the function
    var req = ajaxTest(function (num) {
      var image_temp = '<div class="thumbnail"> <img class="img-thumbnail" src="'+ num + '_1.jpg" /> Image' + num + '</div>';
      console.log(image_temp); //check readout
      image_html += image_temp;
    });

    requests.push(req);  // push the request to the array
  })(i);
}

after(requests).done(function() {
    $('#outputcontent').html(outer_html + image_html + outer_html_end);
});

Note that I've added return $.ajax(...).

BTW: what's the point of defining ajaxTest function? Just put everything in an anonymous callback.

Read more about deferred objects and promises here:

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

http://joseoncode.com/2011/09/26/a-walkthrough-jquery-deferred-and-promise/

or just google it.

EDIT: if the order is important then you just have to tweak the callback function a bit:

var image_html = [];  // array instead of string

// some code...

        var req = ajaxTest(function (num) {
            // ...
            image_html[counter] = image_temp;
        });

// some code...
after(requests).done(function() {
    image_html = image_html.join("");
    $('#outputcontent').html(outer_html + image_html + outer_html_end);
});
freakish
  • 54,167
  • 9
  • 132
  • 169
  • You have just described a `promise`-based solution to a newbie *without explanation*. You might want to explain it in some detail. – iCollect.it Ltd Jul 07 '14 at 09:58
  • @TrueBlueAussie Well, that's quite a big topic. I've added some resources. Hopefuly it will be enough, I don't really want to explain all details. – freakish Jul 07 '14 at 10:01
  • Thanks @freakish - looks like I've got another day of reading ahead of me. I think I see how it works. – Liz Jul 07 '14 at 10:03
  • The SO has not answered my question, but it is likely the image display order is important, else you could just append each image as they load and forget all this complexity. *If* the order is important your answer will not solve the entire problem. Cheers. – iCollect.it Ltd Jul 07 '14 at 10:04
  • Though yes, as a newbie, I'll probably be coming back for help tomorrow too. Thanks for your input thus far. – Liz Jul 07 '14 at 10:04
  • sorry, @TrueBlueAussie the order is important, I do need image1 to sit above image4 (but I don't mind if it loads after it!) – Liz Jul 07 '14 at 10:05
  • @liz_ophiuchus: In that case you need to store each result into an array (indexed by the image number) as well as the other changes shown or similar. `image_html += image_temp` will simply result in the load order. The end result is you need to wait for all results and keep the results in order. Then display after all have finished loading. – iCollect.it Ltd Jul 07 '14 at 10:07
  • @TrueBlueAussie Even without ordering you can still do it if you want all images to appear at the same time. Anyway I've added ordering. – freakish Jul 07 '14 at 10:11
  • That will work, but I find the solution far too complicated for the desired result (it's just an ordered list of images). Have added a simpler alternative using placeholders. Cheers – iCollect.it Ltd Jul 07 '14 at 10:50
  • @TrueBlueAussie True, however your code works slightely different. As I mentioned: in my case all images appear at the same time. In you case they appear irregularly. So it depends on what OP really wants. – freakish Jul 07 '14 at 11:10
  • Yes. I noted those issues. The whole exercise is a waste as the Ajax call result is ignored :) – iCollect.it Ltd Jul 07 '14 at 11:13
  • Ah, I still don't understand why you say the Ajax call is not needed. If I don't know how many images to display (could be 1 to 4) then surely I need to make a server request to check? – Liz Jul 08 '14 at 05:07
0

The current problem is due to using the resulting HTML string, before it has been created via multiple asynchronous calls. You need to either defer that operation until all loads are completed, or change the situation so it is not dependent on final completion (progressive loading looks busier anyway).

Personally I would simply insert placeholders for the images as you loop and insert each as they load. This way the order is retained. You can even do effects (fade etc) on each as they load:

var wrapper = "";
for (var i = 1; i < 5; i++) {
  // Make a placeholder element for each image we expect
  wrapper += '<div class="thumbnail" id="thumb_' + i + '">'

  (function (num) {
    // call the function
    $.ajax({
      type: "GET",
      url: num + "_1.jpg",
      success: function (result) {
          var image_temp = '<img class="img-thumbnail" src="'+ num + '_1.jpg" /> Image' + num;
          // Insert new image into unique element and fade it in
          $('#thumb_'+num).append(image_temp).fadeIn(); 
      }
    });
  })(i);
}

// Append all placeholders immediately - these will be appended to as each image is loaded
$('#outputcontent').html(outer_html + wrappers + outer_html_end);

Update:

As Alnitak points out, the whole exercise is a little pointless as you are ignoring the result returned from the Ajax call anyway, so you might as well drop the Ajax call (which adds no value) and simply build the image elements on the fly server-side :)

iCollect.it Ltd
  • 92,391
  • 25
  • 181
  • 202
  • I'm a little confused by the suggestion of building image elements on the fly - as I said in the original post, there aren't always four images. How will I know whether an image exists and therefore should be displayed without first executing an ajax request? – Liz Jul 08 '14 at 02:11
  • I like the idea of fadeIn(), will have a go at this now – Liz Jul 08 '14 at 02:14
  • a was needed on the end of wrapper, but I've got this working and I *think* I'm starting to get a grip on closure and scope. Thanks ever so much for your help! – Liz Jul 08 '14 at 05:02
  • @liz_ophiuchus: I should have explained more clearly. If the images are on the server, then building the list of images should be done server-side and not on the client. No point making all those Ajax requests for local server files that will simply get pulled down by the browser. Hope all this helped. – iCollect.it Ltd Jul 08 '14 at 07:46