AJAX Progress Bar

Posted on 1/15/2007 @ 6:29 PM in #Vanilla .NET by | Feedback | 22563 views

Reposting: This is another article that got lost when I redid my site. I've received numerous requests to put this back online, so here it is.

In this article we will cover ---

a) Challenges for an ASP.NET application
b) Write a quick custom UserControl for a web based app, specifically a progress bar
c) Make the UserControl programmable via either client side and server side scripting.
d) A quick primer on AJAX
e) Use AJAX to tie up the UserControl to display the progress of a long running job on the server.

The code download for this article can be found here.

Due to the disconnected and stateless nature of web based applications, it is often quite challenging process long running OLTP jobs, and at the same time give reasonable feedback to the end user. One of the most common ways to give feedback to the end user, is by showing a progress bar.

A very common long running operation could be a database operation. I had earlier blogged about showing a progress bar in ADO.NET when reading a huge blob of data from the database. Well, if you notice in that article, what I had been doing was getting the bytes in pre-defined chunks or size from the database. While that operation would still work, not every task that you may need to do from an ASP.NET application could result is so much data being read. Some operations may involve data being written, searched, or simply processed, so lets make things more generic in this article.

Therefore, instead of concentrating on a simple read operation, let me instead demonstrate a generic way to create and use a progress bar in ASP.NET. So the first step is to go ahead and write up a UserControl that has the ability to display a progress bar. This can be easily done using a WebUserControl Library kind of project. The progress bar that I am going to write will consist of the following properties.

1. BackgroundColor
2. ProgressBarColor
3. Value
4. Minimum
5. Maximum

So go ahead and add a new WebUserControl, call it ProgressBar. These properties are really easy to set, here is how you would set the Value property

[DefaultValue(typeof(Color), "Color.Gray")]
public Color
BackgroundColor
{
   get { return
backgroundColor; }
   set { backgroundColor = value
; }
}

The others can be set with code very very similar to the above.

When you add a new WebControl you will note that the Render method has been overridden for you, and that is where your HTML generation goes. Simply add the following code in the Render method.

protected override void Render(HtmlTextWriter output)
{
   StringBuilder sb = new StringBuilder
();
   sb.Append("<table");
   sb.Append(" id = \"");
   sb.Append(
this
.ID.Trim());
   sb.Append("\" height=");
   sb.Append(Height) ;
   sb.Append(" border=0 width=") ;
   sb.Append(Width); 
   sb.Append(" bgcolor=") ;
   sb.Append(System.Drawing.
ColorTranslator
.ToHtml(backgroundColor)) ;
   sb.Append("><tr><td bgcolor=") ;
   sb.Append(System.Drawing.
ColorTranslator
.ToHtml(progressBarColor)) ;
   sb.Append(" width = ") ;
   sb.Append(Width.Value * Value/(Maximum - Minimum)) ;
   sb.Append(" id = \"");
   sb.Append(
this
.ID.Trim());
   sb.Append("Done\"");
   sb.Append("></td><td width=") ;
   sb.Append(Width.Value * (1 - Value / (Maximum - Minimum))) ;
   sb.Append(" id = \"");
   sb.Append(
this
.ID.Trim());
   sb.Append("Left\"");
   sb.Append("></td></tr></table>") ;
   output.Write(sb.ToString());
}

As you can see, based on the properties above, this method simply uses those properties to render a progress bar by showing a table with one row and two columns. The width of the columns is set according to the "Value" property. The full code for the Progress bar can be seen as below --

Image #1

Thats it, now just build the WebControlLibrary to a DLL, and add a reference to a website project. The ProgressBar should appear in your toolbox as shown below. Now just drag drop an instance of the progress bar on to a web form, note that you can set the min/max/colors/etc. Here is a screen cap of me resizing the progress bar and the values being adjusted on the fly - it's pretty awesome.

Image #2

You will also note the button "Click here to start to the Progress Bar", that will start the progress bar - more on that soon. Great, so we have a progress bar ready to go, you can set the "Value" property either in design mode, or programatically, and the progress bar shows that value - pretty sweet.

Actually not very sweet because to set the "Value", you have to first get the "Value" for which you have to run some server side script - which sucks because it involves a postback. So what do we do? So lets see, there are two issues here -

a) To set the value, you need to set the "Value" property at server side.

Okay this is an easy enough to solve problem. All you really do here is, set the width of the TDs in JavaScript. This is why the TDs have actually been named properly in the generated HTML out of the WebUserControl ProgressBar. Given that your ProgressBar instance has id = "ProgressBar1", the progress bar value can be changed at the client side using the following JavaScript --

var done = currentTotalWidth * progress ;
var
left = currentTotalWidth * (1-progress) ;
document.getElementById("ProgressBar1Done").setAttribute("width",done) ;
document.getElementById("ProgressBar1Left").setAttribute("width",left) ;

Note that "progress" is a number less than 1, which reflects the progress bar value, i.e. 0.4 means 40% complete. Which brings us to the next problem --

b) To set the value - you need to know what value to set i.e. reflect the progress of some actual process running on the server.

How do you get information from the server without posting back? Umm .. thats a big problem .. luckily we got AJAX - which is a grandiose name for a really simple concept. Seriously !!! All that means is, you can get a response from the Server by filling an object like XmlHttpRequest or Microsft.XmlHTTP. Basically what that means is that you create a new instance of Microsoft.XMLHTTP as follows --

Internet Explorer --

req = new ActiveXObject("Microsoft.XMLHTTP");

So how the above works is that new ActiveXObject actually creates an instance of a COM component. Certain COM components (such as ActiveXDocuments) are deemed safe i.e. they can be instantiated without the typical security warnings or trust dialogs. XMLHTTP is one of them.

In Firefox you can instead simply use

window.XmlHttpRequest

So the idea being, once you have such an object, you "send" a request to the server, and give a callback method name. The idea being, whenever the server responds, the container will call the "Callback method" you specify. This is done as follows --

req.onreadystatechange = processReqChange;
req.open("GET", url,
true);
req.send(
null);

So an easy enough way to write a browser portable javascript block (IE and FFox) that uses XmlHttpRequest is as shown below ----

function loadXMLDoc(url) {
// branch for native XMLHttpRequest object

if (window.XMLHttpRequest) {
   req =
new
XMLHttpRequest();
   req.onreadystatechange = processReqChange;
   req.open("GET", url,
true
);
   req.send(
null
);
   // branch for IE/Windows ActiveX version
   } else if
(window.ActiveXObject) {
      isIE =
true
;
      req =
new
ActiveXObject("Microsoft.XMLHTTP");
   if
(req) {
      req.onreadystatechange = processReqChange;
      req.open("GET", url,
true
);
      req.send();
   }
}

VERY VERY NICE :-), now you just gotta write the itty bitty javascript - the method "processReqChange". Thats actually darned easy, it looks like as below ---

function processReqChange() {
  
// only if req shows "loaded"
   if
(req.readyState == 4) {
   // only if "OK"
   if
(req.status == 200) {
   // Ok we got the stuph, now lets act on it.
      var
currentTotalWidth = document.getElementById("ProgressBar1").getAttribute("width") ;
      var
progress = req.responseText ;
      document.title = progress ;
      var
done = currentTotalWidth * progress ;
      var
left = currentTotalWidth * (1-progress) ;
      document.getElementById("ProgressBar1Done").setAttribute("width",done) ;
      document.getElementById("ProgressBar1Left").setAttribute("width",left) ;
   }
else
{
      alert("There was a problem retrieving the XML data:\n" + req.statusText);
   }
 }
}

Basically what the above code is doing is, "did we hear back from the server", okay "did we hear an HTTP 200 OK?" .. great .. "What did the server tell us" .. .. and then process upon it. So the magic line is

var progress = req.responseText ;

and then you just use the JavaScript magic mentioned above to resize the progress bar. Now look at Image #2 real quick. What do you see?

"A button to start the progress bar"

Thats right, you need something to kick off the process (see image #2 above). All that button does is call a JavaScript function called "CallMe". Call me calls "RunLoop" with a 500 milisecond timeout delay. RunLoop fires the Ajax request, and clears the time out. After the Ajax request has been sent, using the window.setTimeOut JavaScript method, you basically queue up another call to RunLoop in 500 milliseconds. This loop continues until the progress bar reaches "100% of it's maximum value".

So lets sew everything together.

You have a button to start the progress bar, which simply calls JavaScript:CallMe

CallMe sets up a call to a method called RunLoop which is called every 500 milliseconds

Everytime RunLoop is called, it uses Ajax to make a call to a URL on the server (in our case serverProcess.aspx)

serverProcess.aspx completes the long running batch job, and everytime it is called, it returns the current progress. Now you can make it as hitech as you wish ;-) but in my case it simply writes to a text file, and the value goes from 0,1,2,3---9,0,1,2,3 in subsequent requests. (You can find the full code in the associated code download).

Thats it, now put everything together (or cheat and just download the entire solution over here), and hit F5. Run the project, click on the button that says "Click to Start The Progress Bar". Thats it, your progress bar should be merrily running now :-).

Image #3

Possible ways of improvement -
1. The AJAX request can fetch more than just "How much is completed", you can fetch specific information such as "Here is what I have found so far" in "search through files web page".
2. There is a bug in the code above, The AJAX request doesn't quit :-) after you reach 100%. That is rather easy to fix by moving that logic from server side to client side - I leave that upto you.
3. You could do cosmetic improvements such as change the text of the button when you click on it.

Well I hope you liked this informally written article, I'd love to answer any questions you may have.

Sound off but keep it civil:

Older comments..


On 1/30/2007 3:14:30 PM Jonathan said ..
I've had a request to display a progress bar on a slow running page, for activities happening in the load event of the page, because it takes a while to display when the page loads on some machines. Can you suggest a way to take what you've done and use it to track the time required for a page to load and render?

It's firing off about 4 procedures through ExecuteReader commands, and I'm not sure what I could do to track that and trigger off the progress bar during it.


On 1/30/2007 6:50:22 PM Sahil Malik said ..
Jonathan -

Don't put those procs in Page_Load, instead load the page, and then fire the stored procs in post render, and use an ajax progressbar to track the progress of those progress bars.

SM


On 2/16/2007 10:51:55 PM Tom Skyvers said ..
I have tried the Sample Progress Bar, it was great!


But i am just a beginner in .Net environment especially


when it comes in asp.net, do you also have an AJAX Progressbar


in VB Language? I really need it, i want it to be included in my webpage,


It will served as my time also as my loop in verifying some records in my database.


I wish you could send my a VB Language of that AJAX Progressbar, its really a help.


Thanks in advance.


On 3/27/2007 8:13:55 AM Priyanka Mirani said ..
Thanx 4 such a ultimate code.. its really helpfull.. u have done a good job man.. Keep helping us..


On 5/1/2007 10:26:34 AM Douglas Karr said ..
Would it not be more advantageous to utilize Divs and CSS? That way you can simply apply a style sheet to change the configuration.


On 9/25/2007 2:53:36 AM Chris said ..
I cannot open this control, when I build the app it cannot find the Element ProgressBar


On 1/23/2008 4:37:06 AM Priya said ..
i have downloaded this code and used it,


the progressbar works once but not again..


i am using IE6


pls help me