views:

755

answers:

4

I have read the post here about using setTimeout() during intensive DOM processing (using JavaScript), but how can I integrate this function with the below code? The below code works fine for a small number of options, but when the number of options gets too big my "please wait" animated GIF freezes while the local JavaScript is processing. Thanks!

function appendToSelect() {
    $("#mySelect").children().remove() ;
    $("#mySelect").html(
        '<option selected value="' + obj.data[0].value + '">'
        + obj.data[0].name
        + '</option>'
    );
    var j = 1 ;
    for (var i = 1; i < obj.data.length; i++) {
        $("#mySelect").append(
            '<option value="' + obj.data[i].value + '">'
            + obj.data[i].name
            + '</option>'
        ); 
    }
}
A: 

You would need to rewrite the function to cache the element list, then loop over the list using a counter of some sort.

Then when the counter reaches counter % max_num_before_wait == 0, call timeout back to the function itself.

Make sure to clear the cache and counter at the end of the complete list, or use a secondary function with an extra count parameter.

Geoff
+12  A: 

It just so happens that I was posting about this a moment ago here. Here is a timed loop function:

function processLoop( actionFunc, numTimes, doneFunc ) {
  var i = 0;
  var f = function () {
    if (i < numTimes) {
      actionFunc( i++ );  // closure on i
      setTimeout( f, 10 )
    } 
    else if (doneFunc) { 
      doneFunc();
    }
  };
  f();
}

For your situation this would be used like this:

function appendToSelect () {

  $("#mySelect").children().remove() ;
  $("#mySelect").html(
      '<option selected value="' + obj.data[0].value + '">'
      + obj.data[0].name
      + '</option>'
  );
  var j = 1 ;

  processLoop(function (i){
    $("#mySelect").append(
        '<option value="' + obj.data[i].value + '">'
        + obj.data[i].name
        + '</option>'
    ); 
  }, obj.data.length);

}

You'll want to make sure that you have a closure or some other access to the obj variable within the iteration function.

Hope this helps.

Borgar
thanks for taking the time to answer. I was able to make this code work.
CarolinaJay65
+14  A: 

Here is a solution:

function appendToSelect() {
  $("#mySelect").children().remove();
  $("#mySelect").html(
    '<option selected value="'+obj.data[0].value+'">'
    + obj.data[0].name
    + '</option>'
  );
  obj.data.splice(0, 1); // we only want remaining data
  var appendOptions = function() {
    var dataChunk = obj.data.splice(0, 10); // configure this last number (the size of the 'chunk') to suit your needs
    for(var i = 0; i < dataChunk.length; i++) {
      $("#mySelect").append(
        '<option value="' + dataChunk[i].value + '">'
        + dataChunk[i].name
        + '</option>'
      );
    }
    if(obj.data.length > 0) {
      setTimeout(appendOptions, 100); // change time to suit needs
    }
  };
  appendOptions(); // kicks it off
}

Not as elegant as @Borgar's solution, but you get the idea. Basically, I am doing the same thing, but all in your one function rather than breaking it into a higher-order function like he does. I like his solution, but if you don't, perhaps this will work for you.


EDIT: For those that don't immediately see it, one of the main differences between this solution and @Borgar's is that this solution allows you to set the size of the 'chunks' of data that is processed between each timeout. @Borgar's times-out after every single member of the array is processed. If I get time, I will try to create a higher-order function to handle this so it is more elegant. No promises though! ;)


EDIT: So, here is my adaptation of @Borgar's solution, which allows for setting a 'chunk' size and configuring the timeout value more easily:

function incrementallyProcess(workerCallback, data, chunkSize, timeout, completionCallback) {
  var itemIndex = 0;
  (function() {
    var remainingDataLength = (data.length - itemIndex);
    var currentChunkSize = (remainingDataLength >= chunkSize) ? chunkSize : remainingDataLength;
    if(itemIndex < data.length) {
      while(currentChunkSize--) {
        workerCallback(data[itemIndex++]);
      }
      setTimeout(arguments.callee, timeout);
    } else if(completionCallback) {
      completionCallback();
    }
  })();
}

function appendToSelect() {
  $("#mySelect").children().remove();
  $("#mySelect").html(
    '<option selected value="' + obj.data[0].value + '">'
    + obj.data[0].name
    + '</option>'
  );
  obj.data.splice(0,1); // we only want remaining data   
  incrementallyProcess(function(data) {
    $("#mySelect").append(
    '<option value="' + data.value + '">'
    + data.name
    + '</option>'
   );
  }, obj.data, 10, 100, removeAnimatedGifFunction); // last function not required...
}

Hope that helps - I think this combines the best of both solutions. Notice, the second anonymous function no longer uses the index value, but simply passes in the entire object (with the value and name properties); that makes it a bit cleaner, since the index of the current object really isn't usually that useful when iterating over things, IMO.

I am sure there are still things that could be done to make this even better, but that is left as an exercise for the reader. ;)

Jason Bunting
thanks for the answer. I am going to use this code because I like being able to tweak not only the timeOut time but the chunk of the array that is processed prior to calling timeOut. All I had to add was an else after the setTimeout to hide my "please wait" gif when I am done. Thanks again
CarolinaJay65
No problem - glad I could help.
Jason Bunting
love it. this works great.
Winston Fassett
Glad to hear others are finding it useful too. :)
Jason Bunting
A: 

If you need something simpler, I wrote a jQuery plugin to ease writing of asynchronous loops: jQuery Async.

Using the plugin, your code can be rewritten as:

function appendToSelect() {
    $("#mySelect").children().remove() ;
    $("#mySelect").html(
        '<option selected value="' + obj.data[0].value + '">'
        + obj.data[0].name
        + '</option>'
    );

    /////////////////////////////
    var i = 1;
    $.whileAsync({
     test: function(){ i < obj.data.length; }
     loop: function()
     {
            $("#mySelect").append(
                '<option value="' + obj.data[i].value + '">'
                + obj.data[i].name
                + '</option>'
            ); 
      i++;
     }
    });
    /////////////////////////////
}

Should help the responsiveness. Tweak the 'bulk' and 'delay' option for more control.

Vincent Robert
Thanks for taking the time to answer. I guess this shows the real value of SO...since people can add answers as the become available. I will take a look at your plug-in
CarolinaJay65