Export (0) Print
Expand All
3 out of 5 rated this helpful - Rate this topic

Updating the Display During Lengthy Operations

As of December 2011, this topic has been archived. As a result, it is no longer actively maintained. For more information, see Archived Content. For information, recommendations, and guidance regarding the current version of Internet Explorer, see Internet Explorer Developer Center.

Dave Massy
Microsoft Corporation

February 26, 2001

Contents

So What's Going On?
Yielding Your Script
Timeout
Make the User Wait
Don't Just Block Your Users

Last month I discussed using Web Services from DHTML, and in future months I'll be visiting that topic in much more detail. The more I think about Web Services, the more excited I get about the possibilities it allows for Web applications in the future. It's pretty sad I know, but these things do get me excited. This month, however, I thought I'd cover a more basic topic. Over recent months I've been involved a few long e-mail threads about this issue and I thought it'd be interesting to explain why the issue exists and what are the possible solutions. The initial e-mail that starts the thread usually goes something like this:

I've got a script function that has a while loop that takes a long time to execute. Although I put a statement inside the loop to update the display as the iteration progresses, the display is not updated until the routine has finished.

And they often attach a piece of script like the one below:

<SCRIPT>
function longtime()
{  
   var i=0;
   while (i<=500)
   { 
      d1.innerHTML="Count ="+i;
      i++;
      // following statement represents a lengthy process
      var j=0;   
      while (j<=10000)
          j++;
   }
}
</SCRIPT>

View the sample.

This is not an unexpected question from those new to writing DHTML, and while those of us who have been coding DHTML for some time might immediately respond —"isn't it obvious?"—the issue does deserve a more detailed explanation.

The desired effect of the HTML and script is to show a progress count while the lengthy iteration was executing. The actual resulting experience is pretty bad for the user. The most notable problem is that after pressing the button there is a pause and the user gets a prompt saying something to the effect that the script is taking a long time and would they like to abort it, and at that stage the display is updated with the value of the count. There is another subtler effect that you'll notice prior to the prompt appearing; the button remains in the depressed state and the user cannot interact with the browser at all. The fact that the user is locked out of the browser is the reason that Microsoft® Internet Explorer prompts the user so that they can escape from a situation where script is taking too long.

So What's Going On?

One of the fundamentals of script running in Internet Explorer is that it runs uninterrupted. Hence, when the script above executes, the browser is locked until it has finished. At first this might seem like a poor design and you might consider it a bug, but if you stop and think about the implications of not operating this way, you'll hopefully realise (I'm English so I spell realise with an "s" rather than the "z" that my American colleagues use) that this isn't a bug at all, but rather a logical design.

Let's imagine that the script above did execute as the author intended, and the user saw the count value on the display rise from 0 to 500. In this hypothetical situation, every time the display updated, events such as onresize and onchange would be generated. Should these events be handled in script immediately? Probably not as that could have effects on the script that was running in the original function—if a global variable's value was changed, for example. An alternative would be to queue up all the events to be processed when the original function finally executed, but this too has a number of problems and seems far from efficient. It is useful for script to be able to execute and decide how things should be on the screen and then have all updates occur simultaneously when the function completes, rather than having a gradual display change on different parts of the screen.

So, why does the display update when the prompt to abort the script appears? Well, the display of a dialog box causes the script to yield and thus, the display engine gets an opportunity to update the display to show the current DHTML document tree. The idea of the script yielding is important and is similar to when a car yields to let another vehicle join the flow of traffic. In the browser, when the script engine yields, the display engine is allowed to have a turn, at which point the display engine takes a look at the DHTML document tree and updates it with any changes from the last time it had such an opportunity. I've simplified what goes on within the browser, but the display engine is quite smart at working out which parts of the display have been invalidated and need rendering again, which may in turn cause the layout of the current display to be altered, which in turn forces the redisplay of other parts of the screen and so on. The important point to note here is that yielding the script allows both the display to be updated and the user to interact with the browser. So to have our example script from above function as desired, we need to force the script to yield.

Yielding Your Script

There are some things a developer can do to force the script to yield. Firstly, as we have already seen, a dialog box will force the script to yield, and clearly we don't want to use a dialog box in our example above as we want to update the screen without prompting the user in any way. Although the fact that a dialog box forces the script to yield brings up an interesting issue in regards to debugging script. Sometimes a developer might be scratching their head about why a piece of code is not working as intended and it is quick and easy to put a line of code such as the following into the offending code to find out what the value of the variable mystuff is:

alert("The value of mystuff is: "+mystuff);  

This works pretty well for a lot of circumstances. However, because the alert has forced a dialog box, the script has yielded. This yield can result in the display updating and some of the values of the layout styles being altered. So, the flow of the script that follows will not necessarily be the same as if the alert was not present in the script. A debugging technique I often use instead of an alert is to display the value of the variable in which I am interested in the status bar of the browser:

window.status="The value of mystuff is: "+mystuff;  

This way the flow of the script is not interrupted with dialog boxes appearing.

Another way to force the script to yield is the use of timeouts, and we'll use this technique for our example script.

Timeout

A timeout isn't just a strange concept they use in American football to allow a team to interrupt the flow of the game, it's also a powerful scripting technique that allows script to be called after a certain amount of time has expired. There are two timer methods available, setTimeout and setInterval. They both achieve similar functions with setTimeout giving a single, one-time only callback to the function, while setInterval will repeatedly call back the function at the appropriate intervals. In our case, we're going to use setTimeout.

<SCRIPT>
var i;
function longtime()
{
  i=0;
  timedIterations(); 
}

function timedIterations()
{
   if (i<=500)
   { 
      d1.innerHTML="Count ="+i;
      // following statements represent a calculation that takes time
      var j=0;      
      while (j<=10000)
          j++;  
      setTimeout("timedIterations();", 1);
      i++;   
   }
}
</SCRIPT>
<INPUT TYPE="BUTTON" ONCLICK="longtime();" VALUE="Click Me">
<DIV ID="d1">This is where the count of iterations should display</DIV>
<SCRIPT>
var i;
function longtime()
{
  i=0;
  timedIterations(); 
}

function timedIterations()
{
   if (i<=1000)
   { 
      d1.innerHTML="Count ="+i;
      // following statements represents a calculation that takes some time
      var j=0;      
      while (j<=10000)
          j++;  
      setTimeout("timedIterations();", 1);
      i++;   
   }
}
</SCRIPT>

View the sample.

In the example above, we have taken the original code and separated it into two functions for clarity, with the main function called timedIterations calling setTimeout to call itself back again. The frequency of setTimeout here is set to one millisecond, the idea being that it will get called back as soon as possible. These timers cannot be relied on as being exact for timing purposes down to the exact millisecond, but if you play with the example and set the timeout to 1000 milliseconds you should find it to be pretty accurate to one second per iteration. Of course it's up to you to decide how much you want to break down your processing into chunks for the timed iterations depending on the task being performed.

Make the User Wait

Now this example is well and good, but there is often a requirement that while processing something we want to make the user wait until that processing is complete. The simple way to do this is to disable the input and display the wait cursor:

<SCRIPT>
var i;
function longtime()
{
  i=0;
  b1.style.cursor="wait";
  b1.disabled=true; 
  timedIterations(); 
}

function timedIterations()
{
   if (i<=500)
   { 
      d1.innerHTML="Count ="+i;
      // following statements represents a calculation that takes some time
      var j=0;      
      while (j<=10000)
          j++;  
      setTimeout("timedIterations();", 1);
      i++;   
   }
   else
   {
        b1.style.cursor="";
        b1.disabled=false; 
   }
}
</SCRIPT>
<BODY ID=b1>
</BODY>

View the sample.

In the example above, I added a little code to disable the entire body of the document and display the wait cursor while the iterations were taking place. Hence, the browser continues to update and is not locked from the user, but the user is prevented from interacting with the page and causing other events to happen. Of course this is a fairly blunt approach and you may want to disable some parts of a page and even offer the user an integrated way to abort the functionality.

Don't Just Block Your Users

The main point I'd like to finish on this month is that when coding functionality in DHTML it is important to not block the user out of the browser. Even if you do not want the user to change anything on the page while a lengthy function is executing, it creates a very unpleasant user experience if the entire browser feels like it is locked up. The functionality is available in the browser to allow you to create a pleasant experience for the user while offering them feedback on what's happening. To close, here's a very simple modification to our example that gives us a progress bar that grows as our iterations progress.

View the sample.

 

DHTML Dude

David Massy occasionally wears sun glasses and pretends to be a dude, but when the dark glasses are removed, he works as a technical evangelist on Windows and Internet Explorer technologies, which means he talks to customers of Microsoft about how they might best use the technologies. Before becoming a technical evangelist, Dave worked on the Internet Explorer team as a program manager. Since Dave is originally from England, he is valiantly struggling to educate his American colleagues on how to correctly pronounce "tomato."


  
Did you find this helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft. All rights reserved.