AJAX progress bar

With few lines of JavaScript and CSS you can make a simple AJAX progress bar. JavaScript will periodically ask for progress value and server will respond with XML. Progress value should be extracted from the XML and displayed as width of the DIV element. To start progress bar in this example, please click on the Start button and wait for a second to begin. If you have installed Firebug, open Net tab and watch how this page sends request to the Web server.


More precisely, every 1000ms send_request() function sends request to the server (see setInterval line in polling_start() function). ajax-progress-bar.php returns XML and request_handler() processes received XML. Progress value from XML has meaning of percentage completion of a job.

In the following JavaScript source, please focus to the sending and handling requests. Initialization of the XMLHttpRequest is written in the cross-browser manner and there’s nothing more to add. The onreadystatechange property is a function that receives the feedback. It is important to note that the feedback function must be assigned before each send, because upon request completion the onreadystatechange property is reset. This is evident in the Mozilla and Firefox source.

// create XMLHttp request object in a cross-browser manner
initXMLHttpClient = function () {
    var XMLHTTP_IDS,
        xmlhttp,
        success = false,
        i;
    // Mozilla/Chrome/Safari/IE7+ (normal browsers)
    try {
        xmlhttp = new XMLHttpRequest(); 
    }
    // IE(?!)
    catch (e1) {
        XMLHTTP_IDS = [ 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0',
                        'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ];
        for (i = 0; i < XMLHTTP_IDS.length && !success; i++) {
            try {
                success = true;
                xmlhttp = new ActiveXObject(XMLHTTP_IDS[i]);
            }
            catch (e2) {}
        }
        if (!success) {
            throw new Error('Unable to create XMLHttpRequest!');
        }
    }
    return xmlhttp;
};

// send request to the server
send_request = function () {
    if (number < number_max) {
        request.open('GET', 'ajax-progress-bar.php', true); // open asynchronus request
        request.onreadystatechange = request_handler;       // set request handler    
        request.send(null);                                 // send request
        number++;                                           // increase counter
    }
    else {
        polling_stop();
    }
};

// request handler (started from send_request)
request_handler = function () {
    var level;
    if (request.readyState === 4) {   // if state = 4 (operation is completed)
        if (request.status === 200) { // and the HTTP status is OK
            // get progress from the XML node and set progress bar width and innerHTML
            level = request.responseXML.getElementsByTagName('PROGRESS')[0].firstChild;
            progress.style.width = progress.innerHTML = level.nodeValue + '%';
        }
        else { // if request status is not OK
            progress.style.width = '100%';
            progress.innerHTML = 'Error:[' + request.status + ']' + request.statusText;
        }
    }
};

// button start
polling_start = function () {
    if (!intervalID) {
        // set initial value for current number of requests
        number = 0;
        // start polling
        intervalID = window.setInterval('send_request()', 1000);
    }
};

// button stop
polling_stop = function () {
    // abort current request if status is 1, 2, 3
    // 0: request not initialized 
    // 1: server connection established
    // 2: request received 
    // 3: processing request 
    // 4: request finished and response is ready
    if (0 < request.readyState && request.readyState < 4) {
        request.abort();
    }
    window.clearInterval(intervalID);
    intervalID = false;
    // display 'Demo stopped'
    progress.style.width = '100%';
    progress.innerHTML = 'Demo stopped';
};

Here you can see the source of ajax-progress-bar.php (how is job progress emulated). In your case you will have to calculate percentage of completion and return it in XML format. Caching for ajax-progress-bar.php should be disabled. Many proxies and clients can be forced to disable caching with “header” lines pragma, cache-control and expires.

<?php
// no cache
header('Pragma: no-cache');
// HTTP/1.1
header('Cache-Control: no-cache, must-revalidate');
// date in the past
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
// define XML content type
header('Content-type: text/xml');
// print XML header
print '<?xml version="1.0"?>';
// prepare demo progress value
$progress = (time() % 50) * 2;
?>
<DOCUMENT><PROGRESS><?php print $progress ?></PROGRESS></DOCUMENT>

Download source code: redips6.tar.gz

11/21/2010 – Code tested with JSLint & enabled source download
06/10/2011 – Added limit of how many times to request the server (automatic demo stop)

64 thoughts on “AJAX progress bar”

  1. Seeing the actual code in your /my/ajax-progress-bar.php would complete this example. Otherwise it’s left to the imagination as to how your process produces that XML doc.

  2. @roger – Last listing in post is actually the source of the ajax-progress-bar.php used in this demonstration. It hasn’t much sense, but it good enough for demo. ;)

  3. could you use this for an upload progress bar? if so, what parameters do I change?

  4. Can you tell me how can I implement the php part in C. I am developing an Ajax based file upload progress bar and if i use text file to update data at server end, then progress bar is not updated as expected as Ajax is not able to read the updated values when the file is being updated. Any idea how to implement this in C based CGI.

  5. It’s very good.
    I like this.
    Thanks for share.
    And I wrote something to introduce this project for my readers.
    You can find the post about this in my website.
    If something is wrong,pls figure it out.thanks.

  6. Very nice code, thank you.

    How do I make to stop it automatically whenever it reaches 100% ?

    best regards and thank you
    Pepe

  7. @Pepe – You can modify code inside if (request.status === 200) to look like this:

    if (request.status === 200) {
        // get progress from the XML node and set progress bar width and innerHTML  
        level = request.responseXML.getElementsByTagName('PROGRESS')[0].firstChild;  
        progress.style.width = progress.innerHTML = level.nodeValue + '%';
        // if progress bar value reaches threshold 98  then stop
        if (parseInt(level.nodeValue) >= 98) {
            progress.style.width = progress.innerHTML = '100%';
            polling_stop();
        }
    }
    

    For this example I defined threshold of 98% to stop because PHP page never return 100. Hope this will help. Cheers!

  8. hey,
    I want to adapt your progress bar to show me the progress of a large mysql query,
    can you give me any tips?

  9. @razvan – If the main script takes time to execute the full code, you can have some break ups (landmark points) at some intervals (simple scenario is with insert or update in loop). In this intervals you can write/update progress information to the database. Next, ajax-progress-bar.php will simply read progress information and return current job progress to show percentage of job done. I think this will work …

  10. @kanmani – You have to create ajax-progress-bar.jsp page that will return simple XML with progress value. And next, in send_request() JavaScript function change URL to point to the JSP page. Please try to click on ajax-progress-bar.php to see a XML format used in this example. Your JSP page should produce the same XML format.

  11. When I run this script, it loads another instance of my CGI program every second as opposed to getting the status of the already running program.

    What am I doing wrong?
    My CGI is just returning STDOUT text.
    my request_handler looks like this:

    function request_handler() {
        var r = "";
        if (request.readyState === 4) {   // if state = 4 (operation is completed)
            if (request.status === 200) { // and the HTTP status is OK
                r = request.responseText;
                delayWrite(r);
            } else { // if request status isn't OK
                r = request.responseText;
                delayWrite(r);
            }
        }
    }
    
  12. @Shawn – So, you started some job from browser and want to follow progress of execution. That’s fine, but don’t forget that HTTP is stateless protocol. In other words, HTTP server will imply every AJAX request as separated request. OK, imagine your CGI processes heavy job you want to monitor. When a new AJAX request comes to the HTTP server, a single control process of HTTPD (the parent) will transfer that request to the first free child process. So, a new CGI process will get AJAX request – not the previous one that still executes heavy task.

    If you can somehow read the progress of job execution with separate CGI, that should be the solution for your problem.

    I will give you another scenario for monitoring 10000 inserts to the MySQL database. First I have to be sure that execution time will not be longer then PHP limit (or I will have to increase max_execution_time parameter in /etc/php.ini file). Next, after every 100 inserts in table “A”, I will update counter in the same MySQL database (for example “counter” row in my “progress” table). AJAX progress bar will be directed to read the “counter” row from “progress” table and display it to the user interface. With this solution, I will be able to monitor MySQL insert progress …

    Hope this comment will give you some hints and directions.
    Cheers!

Leave a Comment