[ Team LiB ] Previous Section Next Section

15.10 Displaying an Animated Progress Bar

NN 6, IE 5

15.10.1 Problem

You want to show a progress bar while a long, repetitive script operation takes place.

15.10.2 Solution

Components of this solution consist of a minimum of HTML, a style sheet to give the HTML its look and feel, and scripts to control the display and animation of the progress bar. The HTML included in the page consists of a group of nested div elements, each of which makes up a separate visible portion of the user interface: the wrapper around the entire progress bar, the label text, the empty bar, and the colored bar that expands to the right during the animation:

<div id="progressBar">
    <div id="progressBarMsg">Calculating...</div>
    <div id="sliderWrapper">0%
        <div id="slider">0%</div>
    </div>
</div>

The progress bar is hidden when the page loads, and the rest of its look and feel is controlled by an extensive style sheet described in the Discussion. You can customize the appearance by modifying the style sheet.

Apply the script shown in Example 15-5 to the progress bar. The sequence used to control the progress bar consists of calls to the showProgressBar( ), calcProgress( ), and hideProgressBar( ) functions. See the Discussion about how to repeatedly invoke calcProgress( ) to convey motion to the bar. The page must also include onload event handler calls to two initialization routines: the DHTML API library (Recipe 13.3) and the initProgressBar( ) function:

<body onload="initDHTMLAPI( ); initProgressBar( )">

15.10.3 Discussion

Despite the simplicity of the HTML for the progress bar, it can convey a significant amount of information. Figure 15-4 shows what the progress bar looks like in the middle of its animation.

Figure 15-4. An animated progress bar in action
figs/jsdc_1504.gif

Other than the progress message and initial 0% indicators, the look and feel of the entire bar is controlled by the following style sheet rules:

<style type="text/css">
#progressBar {position:absolute; 
              width:400px; 
              height:35px; 
              visibility:hidden;
              background-color:#99ccff; 
              padding:20px;
              border-width:2px;
              border-left-color:#9999ff; 
              border-top-color:#9999ff;
              border-right-color:#666666; 
              border-bottom-color:#666666; 
              border-style:solid
             }
#progressBarMsg {position:absolute;
                 left:10px; 
                 top:10px; 
                 font:18px Verdana, Helvetica, sans-serif bold 
                }
#sliderWrapper {position:absolute; 
                left:10px; 
                top:40px; 
                width:417px; 
                height:15px;
                background-color:#ffffff; 
                border:1px solid #000000; 
                text-align:center;
                font-size:12px
               }
#slider{position:absolute; 
        left:0px; 
        top:0px; 
        width:420px; 
        height:15px;
        clip:rect(0px 0px 15px 0px);
        background-color:#666699; 
        text-align:center; 
        color:#ffffff; 
        font-size:12px
       }
</style>

Progress bar scripting, as shown in Example 15-5, relies on the DHTML API from Recipe 13.3 and the centerOnWindow( ) function from Recipe 13.7.

Example 15-5. Scripts for the progress bar
<script type="text/javascript" src="DHTMLapi.js"></script>
<script type="text/javascript">
// Center a positionable element whose name is passed as 
// a parameter in the current window/frame, and show it
function centerOnWindow(elemID) {
    // 'obj' is the positionable object
    var obj = getRawObject(elemID);
    // window scroll factors
    var scrollX = 0, scrollY = 0;
    if (document.body && typeof document.body.scrollTop != "undefined") {
        scrollX += document.body.scrollLeft;
        scrollY += document.body.scrollTop;
        if (document.body.parentNode && 
            typeof document.body.parentNode.scrollTop != "undefined") {
            scrollX += document.body.parentNode.scrollLeft;
            scrollY += document.body.parentNode.scrollTop
        }
    } else if (typeof window.pageXOffset != "undefined") {
        scrollX += window.pageXOffset;
        scrollY += window.pageYOffset;
    }
    var x = Math.round((getInsideWindowWidth( )/2) - (getObjectWidth(obj)/2)) + scrollX;
    var y = Math.round((getInsideWindowHeight( )/2) - (getObjectHeight(obj)/2)) + scrollY;
    shiftTo(obj, x, y);
    show(obj);
}
   
// initialize progress bar for IE quirks mode by setting its initial dimensions
function initProgressBar( ) {
    // create quirks object whose default (CSS-compatible) values
    // are zero; pertinent values for quirks mode filled in later
    if (navigator.appName =  = "Microsoft Internet Explorer" && 
        navigator.userAgent.indexOf("Win") != -1 && 
        (typeof document.compatMode =  = "undefined" || 
        document.compatMode =  = "BackCompat")) {
        document.getElementById("progressBar").style.height = "81px";
        document.getElementById("progressBar").style.width = "444px";
        document.getElementById("sliderWrapper").style.fontSize = "xx-small";
        document.getElementById("slider").style.fontSize = "xx-small";
        document.getElementById("slider").style.height = "13px";
        document.getElementById("slider").style.width = "415px";
    }
}
   
// display progress bar
function showProgressBar( ) {
    centerOnWindow("progressBar");
}
   
// update display of bar and percentage completed
function calcProgress(current, total) {
    if (current <= total) {
        var factor = current/total;
        var pct = Math.ceil(factor * 100);
        document.getElementById("sliderWrapper").firstChild.nodeValue = pct + "%";
        document.getElementById("slider").firstChild.nodeValue = pct + "%";
        document.getElementById("slider").style.clip = 
            "rect(0px " + parseInt(factor * 417) + "px 16px 0px)";
    }
}
   
// hide progress bar
function hideProgressBar( ) {
    hide("progressBar");
    calcProgress(0, 0);
}
</script>

The centerOnWindow( ) function is called each time the progress bar is shown (in the showProgressBar( ) function). Thus, if the user resizes the browser window between displays of the progress bar, the bar is still centered on the current window.

Due to the heavy use of borders and padding to define the look and feel of the progress bar, a bit of extra coding is needed to assist with the sizing of the nested div elements. The discrepancies in IE between backward- and CSS-compatible modes have a significant bearing on the various measures. The default specification from the style sheets is tailored for the CSS-compatible world, and runs as-is in Netscape 6 or later and IE 5 for the Macintosh. For IE 5, 5.5, and the quirks mode of IE 6, the initProgressBar( ) function (invoked via the onload event handler) makes critical adjustments to several dimensions.

Three functions control the visibility and animation of the progress bar. Two simply show and hide the bar, but the calcProgress( ) function is the key to the animation. The bar works by adjusting the clipping rectangle of the darker-colored, most deeply nested div element. It is initially set up to be clipped to zero pixel width. Calculations are based on some current value compared against a maximum value—notions that can be applied to any quantifiable operation. The factor (converted to a percentage for display within the bar space) is applied to the right edge of the clipping rectangle.

To experiment with the progress bar, I've created some test functions that simply repeat through a sequence of calculations with a little bit of irregularity thrown in to make the display interesting. A global object named loopObject contains properties to assist with the experimentation. The runProgressBar( ) function represents the kind of function you might be using that would benefit from the progress bar display. The key to successful deployment is a repeating script triggered by setTimeout( ) or setInterval( ) so that the page updates between iterations. Once the target value is achieved, the calcProgress( ) function is invoked with the same values as parameters to allow the bar to display the 100% value briefly, before the progress bar is hidden.

Identifying the kinds of operations to which the progress bar can be applied may not be easy. Prerequisites include an operation that takes sufficient time to warrant the display of the progress bar in the first place. But more importantly, the operations must be something that occur after the page has loaded so that the progress bar div elements are already loaded and modified as needed for quirks-mode versions of IE for Windows.

The operation must also be something for which you have a known target quantity, so that the current progress can be measured against that target. One possibility is monitoring the precaching of images, provided the operation takes place triggered by the onload event handler of the page. Your equivalent of the runProgressBar( ) function should loop through the array of image objects, checking whether the value of each object's complete property is true. If it is, the progress bar is incremented by one fraction of the image array's length, and your function should loop again via the setTimeout( ) method to test the next image in the array. If the value is false, the same index value is passed to the function again (via the setTimeout( )) to try once more. Of course, images don't always arrive from the server in source code order, so the progress bar is likely to be inconsistent in its progress. Also, if the images are already in the browser cache, the loop through the array may actually slow down the user's access to the finished page.

A progress bar is the right solution when the user might get impatient with something that is truly going on behind the scenes. Tolerance for delays is greatly extended when you entertain in the interim.

15.10.4 See Also

Recipe 12.7 for hiding and showing an element; Recipe 13.1 for how to make an element positionable; Recipe 13.3 for details on the DHTML API library; Recipe 13.7 for centering a positioned element in the browser window.

    [ Team LiB ] Previous Section Next Section