- Displaying a "Please Wait" Page
- Displaying a Progress Bar Graphic
- Implementing a Staged Page Load Process
- Summary
Displaying a Progress Bar Graphic
A static "please wait" message is fine, but it could not be described as eye-catching, and it gives no indication that anything is actually happening. The server could die while the message is being shown, leaving you still staring at the "please wait" message three days later. It's nice to have some kind of indication that the Web site is still alive and really is working furiously to generate the results you asked for.
Unfortunately, with the way that Web browsers and HTTP work, this isn't easy to achieve. Each request/response is treated as a single unit of operation, and there is no persistent connection over which status information can be passed. There are ways around this, of course, but they tend to hit performance and cause undue server loading. You'll see an example of this in the section Implementing a Staged Page Load Process, later in this chapter.
An alternative is to display something that makes it look like the browser is working hard but actually bears no relationship to what's happening on the server. When you do this, you avoid the need for extra connections while the main process is taking place, and yet you still satisfy the user's desire to see something happening. The simplest solution is to use an animated GIF file in the page instead of or in addition to the "please wait" message.
Figure 3.6 shows the "please wait" page for this example. Instead of just a text message, you now also have a progress bar that appears to reflect the state of the long-running process that is generating the results the user is waiting for.
As intimated earlier, however, the progress bar is an illusion in that it will keep moving, regardless of whether the page takes a minute or a month to appear. But by carefully choosing the timing of the animation in the GIF file to match the anticipated average response times for average users, you can get it to look quite realistic.
Figure 3.6 Displaying a progress bar while loading another page.
Other than the appearance of the progress bar, the remainder of this example looks the same as the previous example, which displays just the "please wait" text message. Therefore, the following sections concentrate on what's different in the declaration of the HTML, the server controls, and the code used to drive this page compared to the previous example.
Achieving True and Accurate Status Displays
To achieve a real page-loading status display, you can arrange for your server-side code to flush chunks of output to the client as it carries out the processing required to generate the results. These chunks of output could be client-side script that writes status details within the current browser page or even just simple <img> elements that load images to indicate progress of the operation. As an example, the MSN Expedia Web site (http://www.expedia.com) flushes partial page output to the browser, as you can see if you view the source of the page while it's searching for that holiday in Florida you keep promising your kids. However, it also uses a "dummy" animated graphic, just as this example does, which effectively indicates nothing about the actual underlying process of the operation.
The Progress Bar Animated Graphic Files
We provide four different versions of the animated progress bar graphic in the images folder of the examples you can download for this book (from http://www.daveandal.net/books/6744/). The only difference between them is the speed at which the progress display moves from left to right. The details of these graphics files are summarized in Table 3.1.
Table 3.1 The Progress Bar Animated GIF Files for This Example
Filename |
Description |
progressbar10.gif |
The indicator progresses at a steady speed from left to right in approximately 10 seconds, and it remains at the fully right (complete) position for 10 seconds before starting again. |
progressbar20.gif |
The indicator progresses at a steady speed from left to right in approximately 20 seconds, and it remains at the fully right (complete) position for 10 seconds before starting again. |
progressbar30.gif |
The indicator progresses at a steady speed from left to right in approximately 30 seconds, and it remains at the fully right (complete) position for 10 seconds before starting again. |
progressbarlog.gif |
The indicator progresses in logarithmic fashion from left to right in approximately 30 seconds, starting quickly and then getting slower. It remains at the fully right (complete) position for 10 seconds before starting again. |
Displaying the Progress Bar Graphic
In theory, building the progress bar sample page should be easy. You just have to insert an <img> element into the section of the page that is displayed for Stage 2 of the process in the previous example, and you're done, right? However, most Web developers approach these trivial tasks with trepidation and with a knowledge gleaned from experience that nothing ever works quite as you expect when dealing with Web browsersespecially Web browsers from different manufacturers.
It turns out that trepidation is definitely justified here. Simply adding an <img> element fails to work properly because as soon as the redirection is initiated by the <meta> element, most browsers stop loading any images for the current page. In this case, unless the progress bar is already cached (and the server is extremely responsive when the browser checks whether the file has changed since it was cached), the result is a "missing image" placeholder instead of a progress bar.
The solution to this problem is to force the browser to delay for a few secondslong enough to load the progress bar graphicbefore beginning the refresh process that requests the next page. You can set this delay to 3 seconds in the sample page by changing the content attribute you add to the <meta> element in the Page_Load event handler:
mtaRefresh.Attributes.Add("content", "3;url=" & sRefreshURL)
Now the page works fine in recent Netscape, Mozilla, and Opera browsers. But it still doesn't work properly in Internet Explorer. It seems that Internet Explorer "turns off" the animation in animated GIF files as soon as a new page is requested. After the 3-second delay, the progress bar just stallswhich ruins the whole effect! So, for Internet Explorer, you have to find an alternative approach, as described in the following sections.
An Alternative Page-Loading Technique for Internet Explorer
We experimented with several seemingly obvious approaches to loading the progress bar graphic and reloading the page using client-side script in Internet Explorer, all to no avail. It seems that the only way to circumvent the issue with the stalled animated graphic is to find a completely different way to load the next page (that is, reload the current page with the customer ID in the query string).
Internet Explorer 5 and higher have access to the MSXML parser component; it is part of a Windows installation and is distributed with Internet Explorer as well. Part of the MSXML parser component is an object named XMLHTTP, which you can use to request a resource from the server in the background while a page is loaded and displayed in the browser.
The XMLHTTP object is instantiated and manipulated with client-side script within a Web page, and it exposes properties and methods that allow you to make GET and POST requests to a server both synchronously and asynchronously. Although it is ostensibly designed for fetching XML documents, it works equally well fetching any type of resource, including HTML pages that probably aren't fully XML (or XHTML) compliant.
Loading Pages with the XMLHTTP Object
The process for using the XMLHTTP object is relatively simple, especially if you are happy to load the new page synchronously. You can create an instance of the XMLHTTP object by using the following:
var oHTTP = new ActiveXObject("Microsoft.XMLHTTP");
Next you open an HTTP connection, specifying the HTTP method (usually "GET" or "POST"), the URL of the target resource, and the value false to indicate that you want synchronous operation. Then you can use the send method to send the request:
oHTTP.open("method", target-url, false); oHTTP.send();
After the response has been received from the server, you test the status property (the value of the HTTP status header) to see if it is 200 (which means "OK") and extract the page as a string from the XMLHTTP object by using the following:
if (oHTTP.status == 200) sResult = oHTTP.responseText; else // an error occurred
However, if you use synchronous loading, the browser will not respond to any other events (including animating the GIF file) while the request for the next page is executing. Instead, you need to use asynchronous loading to allow the browser to carry on reacting as normal while the server creates and returns the new page.
Asynchronous Loading with the XMLHTTP Object
For asynchronous loading, you first have to specify the name of a callback function that will be executed each time the readystate property of the XMLHTTP object changes and specify true for the third parameter of the open method:
oHTTP.onreadystatechange = myCallbackHandler; oHTTP.open("method", target-url, true); oHTTP.send();
The callback function you specify will be executed several times as the XMLHTTP object fetches the response from the server. When the response is complete, the value of the readystate property will be 4, and at that point you can test for an error and extract the page as a string:
function myCallbackHandler () { if (oHTTP.readyState == 4) { if (oHTTP.status == 200) sResult = oHTTP.responseText; else // an error occurred } }
Using the XMLHTTP Object in the Progress Bar Sample Page
Listing 3.5 shows the client-side code included in the progress bar sample page. It works exactly as just demonstrated, with the only additions being a test to see that an instance of the XMLHTTP object was successfully created and the display of any error messages in a <span> element, located below the progress bar graphic in the page.
Listing 3.5 Loading the Results Page with XMLHTTP
<script language='javascript'> <!-- // variable to hold reference to XMLHTTP object var oHTTP; function loadTarget(sURL) { // create instance of a new XMLHTTP object oHTTP = new ActiveXObject("Microsoft.XMLHTTP"); if (oHTTP != null) { // specify callback for loading completion oHTTP.onreadystatechange = gotTarget; // open HTTP connection and send async request oHTTP.open('GET', sURL, true); oHTTP.send(); } else { document.all['spnError'].innerText = 'ERROR: Cannot create XMLHTTP object to load next page'; } } function gotTarget() { // see if loading is complete if (oHTTP.readyState == 4) { // check if there was an error if (oHTTP.status == 200) { // dump next page content into this page document.write(oHTTP.responseText); } else { document.all['spnError'].innerText = 'ERROR: Cannot load next page'; } } } //-->
Information on the XMLHTTP Object
You can find a full reference to the XMLHTTP object (effectively the XMLHTTPRequest interface) in the MSDN library, at http://msdn.microsoft.com/library/en-us/xmlsdk30/htm/xmobjxmlhttprequest.asp.
One interesting point about this listing is in the gotTarget callback handler. After you've extracted the complete content of the new page as a string, you simply write it into the current browser window, using the client-side document.write method. This replaces the current content, giving the same output as in the first example in this chapter, after the main customer lookup process has completed (refer to Figure 3.5).
What you've actually achieved here is to reload the same page again in the background, while still at Stage 2 of the process (displaying the "please wait" message and progress bar) and then use it to replace the current page. But because the URL you request contains the customer ID in the query string this time, the new page generated by the server will be the one for Stage 3 of the process (containing the DataGrid control, populated with the results of the database search). Altogether, this is a neat and interesting solution!
The Changes to the HTML and Server Control Declarations in This Example
The only remaining features of this example that we need to examine are how to initiate the client-side code that loads the results page and how to handle cases where client-side scripting is disabled in the browser. In the HTML section of the page, you declare the <body> element as a server control this time, by adding an ID and the runat="server" attributejust as you did for the <meta> element earlier in this chapter:
<body id="tagBody" runat="server">
Then, in the Page_Load event handler, you can add an appropriate onload attribute to the opening <body> tag in the server-side code. Listing 3.6 shows the changed section of the Page_Load event handler. The only section that differs in this example from the first example is the part where the postback from Stage 1 occurswhere you are generating the "please wait" page for Stage 2 of the process.
Listing 3.6 The Page_Load Event Handler for the Progress Bar Example
If Page.IsPostback Then Dim sRefreshURL As String = Request.Url.ToString() _ & "?custID=" & txtCustomer.Text ' if it's IE, need to load new page using script because ' the META REFRESH prevents the animated GIF working If Request.Browser.Browser = "IE" Then tagBody.Attributes.Add("onload", "loadTarget('" _ & sRefreshURL & "');") ' set META REFRESH as well in case script is disabled ' use long delay so script can load page first if possible mtaRefresh.Attributes.Add("http-equiv", "refresh") mtaRefresh.Attributes.Add("content", "30;url=" & sRefreshURL) Else ' not IE so use META REFRESH to start loading next page ' allow 3 seconds for progress bar image to load mtaRefresh.Attributes.Add("http-equiv", "refresh") mtaRefresh.Attributes.Add("content", "3;url=" & sRefreshURL) End If frmMain.Visible = False divWait.Visible = True Else ...
You use the ASP.NET Request.Browser object, which exposes a property also named (rather confusingly) Browser. This property indicates the browser type, and if it is "IE", you know that you are serving to an Internet Explorer browserso we can add the onload attribute to the <body> element by using the Attributes collection of the HtmlGenericControl class that implements it in ASP.NET. The result, when viewed in the browser, looks like this:
<body id="tagBody" onload="loadTarget('/daveandal/books/6744 /loadwait/progressbar.aspx?custID=a');">
You also add a "catch all" feature in case scripting is disabled, by setting the attributes of the <meta> element. In this case, the <meta> element will cause a page reload after 30 seconds. You can also see in Listing 3.6 the changed value of the content attribute that you apply for nonInternet Explorer browsers, to allow the progress bar graphic to load before the redirection commences (as discussed earlier in this chapter).
Checking for the Version of Internet Explorer
In theory, you should test for the browser version as well as the type because the XMLHTTP object is available only in version 5 and higher of Internet Explorer. However, the "catch all" you build in for when scripting is disabled will also make the page work (after a fashion) on earlier versions of Internet Explorer. Whether anyone is still using version 4 or earlier, with all the security issues inherent in those versions, is open to discussion.