views:

93

answers:

4

I am using javascript to run a XMLHttpRequest to a PHP script which returns data. Basically I want to be able to provide the user with a progress bar (instead of a spinning circle or something) that shows the progress of getting and receiving the data. I know if I was getting a file, I could just check the content length header and use that, but in the case of a script, you don't know how much data it's retrieving.

The answer to this might be as easy as, "it's not possible," because right now it seems that way. But in conclusion: How do you monitor progress of a running script (php) over and XMLHttpRequest?

+1  A: 

Off the top of my head you could use 2 ajax requests. One to start and wait for the job to complete, and another to check on the job progress. I'm pretty sure most browsers can do at least 2 ajax requests at a time.

The PHP script (Lets call it job.php) that's actually doing the job can update the session variable $_SESSION['job_progress'] with the percentage the job is complete.

You have another PHP script (Lets call it progress.php) that echos that value, i.e.

<?php echo $_SESSION['job_progress'];

Client side you fire off your ajax request to job.php. You have another ajax request to progress.php that runs every 3 seconds. You update your progress bar with the value returned.

You could also do this with one ajax request if the request to job.php returns before the job is finished. Then you can keep using a single ajax request to ping the progress.php script.

mellowsoon
I think he wants to know how many bytes of a file requested with XHR he's received so far.
Gordon
@Gordon - The same concept applies.
mellowsoon
PHP doesn't keep a record of how many bytes it's sent over a given connection so I don't really understand how using a second connection to ask it would help. If you'd set PHP off executing a job that would take a long time to run, you can use your solution to ask it how long it thinks before the job is complete, but in the case of a job that finishes very quickly but results in a lot of data to be sent this approach wouldn't work so well.
Gordon
A: 

In some browsers (firefox for one), onreadystatechange with readyState 3 (i.e. loading) is invoked mutiple times so download progress could be monitored.

Also, in some browsers the responseText property contains the result returns so far, and it could be examined to get some idea of the progress.

However, Internet Explorer (at least for IE7, not sure about later) does not support this, and it is an error to query responseText or responseBody for readyState 3. I have also heard the IE only calls onreadystatechange once with readyState 3 which would make it pretty useless for your purpose, but I would suggest testing it out.

Yasir
A: 

Create a session. (Probably, you already have one). Create a UID for the query, when you request it to start processing.

Store somewhere on the server (in database, in file, etc) a progress together with the SID+UID, as the query progresses.

Use a second ajax request with timer to poll the progress by SID+UID.

*You can get by only UID, probably, but I've found it to be more manageable when you also could monitor tasks by user/session.

wizzard0
A: 

Hi Evan,

If you're using FireFox (and I'm fairly sure most other browsers other than IE), then there is indeed a way to report how much data has been transferred during an XHR operation. If the operation in question sends the correct header, it's fairly easy to use this information to calculate the percentage of the data downloaded.

I wrote this code for determining the percentage of data transferred in an XHR operation years ago, so I apologize for it not reflecting the years of coding experience I've gained since. I almost certainly wouldn't write it this way now! Still, I managed to fish it out, and hope it's of use for you.

At the time this was written, IE7 was the latest version of Explorer available, and I remember the code didn't work in that, hence it contains code to prevent it initializing under IE. I've never tried this code out under version 8 or the beta of version 9, and it may indeed work in those versions as well, but I can't vouch for it. If you can get it working in a new version of IE, please let me know!

It works by running code in beforeSend (a callback jQuery provides for code you want to run before starting an ajax request) to set up a Javascript interval (in the code I've put 50 miliseconds, which is probably far too often. 200 miliseconds should still be plenty, and put less strain on the system). Every time the interval timer fires, it runs a function that looks at the responseText attribute of the XHR request. The responseText attribute holds the raw text of the data received thus far. By counting how many characters are in there with the length() string method, we can work out how many bytes have been collected so far.

As far as working out the percentage of total data to be sent, this will require that your server side code sends a content-length header with an accurate count of how many bytes it is going to send. This will require a little cleverness on your part, but shouldn't prove too difficult. If you send an accurate content-length header, then it is used to calculate a percentage of data received so far. If you don't set a content header, then the amount of data received so far is displayed instead.

<script type="text/javascript">
$.ajax ({
    beforeSend  : function (thisXHR)
    {
        // IE doesn't support responseText access in interactive mode
        if (!$.browser.msie)
        {
            myTrigger = setInterval (function ()
            {
                if (thisXHR.readyState > 2)
                // When there is partial data available use it to determine how much of the document is downloaded
                {   
                    var dlBytes = thisXHR.responseText.length;
                    if (totalBytes == -1)
                        totalBytes  = thisXHR.getResponseHeader ('Content-length');
                    (totalBytes > 0)?
                        $('#progress').html (Math.round ((dlBytes / totalBytes) * 100) + "%"):
                        $('#progress').html (Math.round (dlBytes / 1024) + "K");
                }
            }, 50); // Check the status every 50 miliseconds
        }
    },
    complete    : function ()
    {
        // Kill the download progress polling timer
        if (myTrigger)
            clearInterval (myTrigger);
    }
});
</script>
Gordon