There and Back Again

AJAX File upload Progress

Example of how to use the newest version is available in another post.
Update
There is a new version of this code that follows the same approach but uses HTML_AJAX instead of JPspan.
You can view the new demo, and view the code in websvn.

Also note that the server isn’t setup to accept files larger then 8meg, so anything bigger will fail.

Also I’m looking for someone to help me improve the error handling, if your interested in getting involved and making some regular releases of the new code let me know.

Original Post below

A couple days ago I found an interesting ruby on rails project. It uses AJAX to update a progress bar as the file uploads. The trick is a patch to rails for getting upload status and doing the upload in an iframe so that the main page is still active.

So to replicate this I just had to find a patch that provides upload status in PHP and then implment my little iframe upload widget.

I found the PHP with a little work from google: Upload Progress Meter

First you need to install the patch and the extension, the included instructions are easy to follow. The only problem I found is that: upload_progress_meter.store_method = “file” had to be set in my php.ini before thing would work.

I also ran into a JPSpan problem, if your having network problems the status call might take longer then 1 second, and you’ll get Call in Progres error alert. This can be fixed with the current version of JPSpan but i’d like to see some api added to help. The proxied objects need some type of inProgress call to make this an easy fix.

One item of note is that the extension only provides information in a way to provide 1 progress bar per form no matter how many files. The javascript code is setup so multiple forms could be on a page at once and both uploading, but this hasn’t been tested.

Here is the demo you’ve been waiting for, for most connections a 250k file will be enough to see something besides connecting and complete.

Also if anyone has the time and skills to review the php patch and see what it would take to get integrated, please let me know. I haven’t heard from its author so I don’t know why its not integrated but it just seems crazy to have a 3K patch that is this useful only available to those who are willing to patch.

Code walk through

So the basic flow happens like this.

Display a page with a form:
this page has a hidden iframe, a hidden progress div, and some extra javascript code

Select the file to upload and submit the form:
The form has a target of the hidden iframe so even though the throbber starts the main page view won’t be getting new content when the upload is done.

The form onclick handler fires a setup function:
The function finds the progress div and shows it, it also registers a function to update the status ever second.

The update function fires every second:
This functions checks a counter to see if there are any more divs to update, if the counter is 0 it stops the update function from firing again
The function creates a remote proxy object to the php class UploadProgressMeterStatus if it hasn’t already been created
The function calls the get_status method on the proxy object with a list of all the progress divs and UPLOAD_IDENTIFIER’s that we need status for.
The function exits

The get_status method on the php class is called
The method calls upload_progress_meter_get_info() for each passed in identifier, the information is formated to a percent and a message which is returned

The callback function for get_status is called when the PHP class returns data
The callback updates the progress div
If were at 100% the we decrement our progress div counter and remove us from the list of divs to be updated

The iframe can also load a page once the file upload is done, it doesn’t currently do anything.

Example Code

This is really the only interestin php code in the project, and its not that interesting. When you have the Upload Progress Meter extension installedany form that is doing a file upload and has a hidden var called UPLOAD_IDENTIFIER can be tracked. The identifier has to be passed into the upload_progress_meter_get_info function so you have to keep track of it on the javascript side. Here we just pass those ids in, do a bunch of formating on the resulting array, and return the results. Note the returned array isn’t documented anywhere so this code and the code in the example php code provided with the extension is the best place to start if you want to do something else with it.

       /**
         * Get the status of all uploads passed in
         */
        function get_status($ids) {
                $ret = array();
                foreach($ids as $id => $upId) {
                        $ret[$id] =  new stdClass();

                        $tmp = upload_progress_meter_get_info($upId);
                        if (!is_array($tmp)) {
                                $ret[$id]->message = "Complete";
                                $ret[$id]->percent = "100";
                                break;
                        }

                        if ($tmp['bytes_total'] < 1) {
                                $percent = 100;
                        }
                        else {
                                $percent = round($tmp['bytes_uploaded'] / $tmp['bytes_total'] * 100, 2);
                        }

                        if ($percent == 100) {
                                $ret[$id]->message = "Complete";
                        }

                        $eta            = sprintf("%02d:%02d", $tmp['est_sec'] / 60, $tmp['est_sec'] % 60 );
                        $speed          = $this->_formatBytes($tmp['speed_average']);
                        $current        = $this->_formatBytes($tmp['bytes_uploaded']);
                        $total          = $this->_formatBytes($tmp['bytes_total']);

                        $ret[$id]->message = "$eta left (at $speed/sec) $current/$total($percent%)";
                        $ret[$id]->percent = $percent;
                }
                return $ret;
        }

On the javascript side there is a bit more code, but none of its all that complex. This code is used to show the progress bar and give it some initial values. The main things to notice are that were add an update method to the div, this is a nice trick since it allows for runtime extension of objects in the DOM, and it will make updating things nice and easy in the other functions. Were also adding a getFirstDivByClass method to the div, I’m doing this so I don’t have to have so many divs to track, the classes only have to be unique inside the progress bar for this to work and thats much easier to achieve.

/**
 * Shows a progress bar and sets it to 0
 */
function UploadProgressMeter_EnableProgress(progress_id) {
        var progress = document.getElementById(progress_id);
        progress.style.display = 'block';
        progress.percent = 0;
        progress.message = "Connecting";

        progress.update = function() { this.getFirstDivByClass('bar').style.width = this.percent+'%'; this.getFirstDivByClass('message').innerHTML = this. message; }

        progress.getFirstDivByClass = function(className) {
                var nodes = this.getElementsByTagName('div');
                for(var i = 0; i < nodes.length; i++) {
                        if (nodes[i].className == className) {
                                return nodes[i];
                        }
                }

        }

        progress.update();
}

The code below calls the remote proxy and creates the callback function to handle the results. The is one area where improvements could be made. First there should be a check if there is currently a call in progress. Then it might also be smart to call the server less (especially on large files) and just generate the stats from the current download rate. This would add some complexity but would allow the progress bar to update smoothly and would allow the server calls to get down to once every 5 or 10 seconds.

If you don't do a lot of javscript programming its worth nothing the use of for(var prop in result) and delete UploadProgressMeter_active[prop];

for(var prop in result) is how you loop through the properties on an object, this allow you to use them as associative arrays (just watch for methods on the objects since you'll loop through them too).

delete UploadProgressMeter_active[prop] is the equivalent of unset($array['key']);


/**
 * Update the progress bars of all the current bars
 */
function UploadProgressMeter_Update() {
        if (UploadProgressMeter_count == 0) {
                clearInterval(UploadProgressMeter_intervalId);
                UploadProgressMeter_intervalId = false;
                return;
        }

        if (UploadProgressMeter_remote == false) {
                var callback = {
                        get_status: function(result) {
                                for(var prop in result) {
                                        if (prop != "toString") {
                                                document.getElementById(prop).percent = result[prop].percent;
                                                document.getElementById(prop).message = result[prop].message;
                                                document.getElementById(prop).update();

                                                if (document.getElementById(prop).percent == 100) {
                                                        UploadProgressMeter_count--;
                                                        delete UploadProgressMeter_active[prop];
                                                }
                                        }
                                }
                        }
                }
                UploadProgressMeter_remote = new uploadprogressmeterstatus(callback);
        }

        UploadProgressMeter_remote.get_status(UploadProgressMeter_active);
}

Code List

Updates

You may have noticed that the demo has stopped working a couple times. This was related to 2 things and there things you might want t think about if your going to use the patch. First it writes tmp files and fails silently if the directly no longer exists (darn /tmp cleaning scripts). Second its on my php5 server which has some users who are pushing the envelope which required me to upgrade to the php 5.1 beta and I forgot to repatch. Repatching wasn't a big deal though I did have to move where a function was declared to get the extension to compile in gcc 4 (I upgraded to fedora core 4 as well).

Anyhow the demo is working and it should stay working as long as I remember to repatch with each upgrade.

New Version

This code has been updated to work with HTML_AJAX and to handle error conditions better. I haven't done a formal release but you can grab it from svn.

Check it out from: http://svn.bluga.net/HTML_AJAX/UploadProgressMeter/trunk/

Or use websvn to get a tarball: http://websvn.bluga.net/wsvn/HTML_AJAX/UploadProgressMeter/trunk/?rev=0&sc=0

Also if your interested in helping out with the Upload Progress Meter let me know

249 thoughts on “AJAX File upload Progress

  1. Totally Free Ajax Upload Script

    I have written an AJAX script that has worked well for a few projects of mine, and it’s completely free. I plan to keep it that way, as well. If y’all have any thoughts on it, I’m happy to hear about them in the forum.

  2. Namrata

    hi

    this is nice idea to use the progress meter i have run it successfully but i dont found the file which i m going to upload is uploaded anywhere in my pc …so is the file saved or not?? and is the uploaded file is saved then where is it??

    how to upload second file without refreshing the browser??

    one more thing,….here where ajax is used?? i think that is not used any concept of ajax

    so why it is mentoined with AJAX???

  3. Joshua Eichorn Post author

    Namrata:
    You have to write the code where the example says handle file upload. If you take a look at the new version it will be clearer what to do.

    At its most basic level AJAX is just sending data to your server without reloading the page. Were using 2 AJAX techniques to make this work. One is a form targeted at a hidden iframe. This lets us do an upload without reloading the page. The other is too poll the server using XMLHttpRequest to get the status of the upload so we can build that status bar.

    On the patch front PHP 5.2 has support for the server side portion without a patch. It also gives us more information so the hacks that use perl can go away pretty soon.

  4. Alexander

    We’ve tried to use an AJAX solution for an upload progress bar, but it proved too unreliable, occasionally it wouldn’t work on certain browsers, the vendor said this is because certain installations of IE can “break” AJAX. Is this true?

    Searching the web, there doesn’t seem to be any sure-fire way of getting this functionality for large (>10mb) files, although I’ve not tried your solution yet. Is it 100% reliable, and cross-browser?

  5. Harsh

    Hi, i tried your code. eg given on this link http://php5.bluga.net/UploadProgressMeter/demo.php.

    but i am getting an error message while trying to upload the file, error is : [server_error]Syntex error while calling uploadprogressmeterstatus.get_status()

    so, i got confused, what should i do.
    And, yes what i have to do with patch, i mean you told that i have to install it on server, but how to install it, and exactly where to install it?

  6. Joshua Eichorn Post author

    Harsh: For starters you want the new version of the code, its linked at the top of this post. Also you’ll need to install the patch or use php 5.2 and upgrade the status call.

  7. Harsh

    hi, i cant use new version of code, because i dont want that progress bar in popup, i just want it as it is in old version. I have downloaded those files, can you ust explain me what changes i have to do after i have downloaded it? and where i have to put all the files? and how to install patch? Right now i have put the downloaded folder in my root directory. And it is giving a javascript error : [server_error]Syntex error while calling uploadprogressmeterstatus.get_status(), every second
    I hope you got my problem.

  8. Irfan

    hello joshua!! how are you?

    i’ve trying to use your php program and I had a problem:

    when I try to upload some file its print an error:

    [Server_error] Only variables shold be assigned by reference while calling uploadprogressmeterstatus.get_status()

    you can help me?

    Regards,

  9. Greg

    Hi Joshua,

    If those who do not wish to install pear, is there any way to use the old version without getting the server_error?

    Thanks,

    Greg

  10. Joshua Eichorn Post author

    Greg: If you don’t want to install HTML_AJAX using pear you need to set jsClientLocation on your HTML_AJAX_Server instance.

    $server = new HTML_AJAX_Server();
    $server->jsClientLocation = ‘/path/to/html_ajax/js’;
    $server->handleRequest();

  11. Greg

    Hi Joshua,

    Sorry to such a pain but I was trying to get the old version working (without HTML_AJAX) but I kept getting the server_error. I tried placing that snippet of code into the new version and got an error when trying to instantiate the class. Which file is suppose to instantiate that class? I’m sorry to be a pain, it’s just I have limited access to the server and I would really like this feature on my script.

    Thanks for your help!

    Greg

  12. Greg

    Hi Joshua,

    I’m very sorry. I tried following the installation instructions, but I just do not seem to understand where I go about setting the jsClientLocation.

    Thanks for your patience,

    Greg

  13. Greg

    Hi Joshua,

    After hours of trying, I finally figured out why that wasn’t working. The function is clientJsLocation and not jsClientLocation.

    Thanks for your help!

    Greg

  14. Axin

    Hello Joshua
    You are great! my code is work, but I don’t know the file where going,
    I find the note “//move the file(s) where they need to go” in demo.php, can you tell me if I want move the file to C:, How it’s going.
    Thanks for your help!

    Axin

  15. Nik600

    Hi
    i am trying to do a myself php/ajax interaction, as i will understand how to manage all.

    I’ve created a form like this:
    //////////////////

    //////////////////

    a file demo.php
    //////////////////
    $file_name=$_FILES[“file”][“tmp_name”];
    echo “$file_name ok”;
    //////////////////

    and a file info.php
    ////////////////////
    $data=uploadprogress_get_info(“18”);

    echo “”;
    echo var_dump($data);
    echo “”;
    ////////////////////

    if i reload info.php during the upload, i get :
    NULL

    Where am i wrong? shall i initialize the uploadprogress in demo.php?

    many thanks for your help
    Nik600

  16. Joshua Eichorn Post author

    There are a couple possible problems.

    One 18 isn’t the upload identifier specified in the form thats doing the file upload (hidden field named UPLOAD_IDENTIFIER)

    The extension might also not be able to write to its tmp dir for whatever reason. The upload progress extension should be writing a entry in the apache error log saying what file its trying to write too.

    Or it could be something else all togther im not sure what your setup is from your comment

  17. Nik600

    ok, i’ve understand where is the problem.

    The hidden field UPLOAD_IDENTIFIER must be placed fist of the file field in the form.

    many thanks

  18. Pingback: Ladebalken whrend Dateiupload - XHTMLforum

  19. KS

    Well, it is really frustrating to read a comment that does not make sense. I really appreciate the effort and wisdom that the author put in the script, but if someone may clarify this comment by putting a sample script that works that would be great. I saw many users raised the same question but none of the answers gave a clear and working solution. I have tried to use
    //
    move_uploaded_file($_FILES[‘userfile’][‘tmp_name’], ‘./’);
    //
    and many other combinations but nothing works. Please advice. Thank you very much!!!

  20. Pingback: Daniel Guerrero Thoughts » Blog Archive » PHP 5.2 and Upload Progress Monitor

  21. Pingback: wagg.it

  22. Pingback: Cabeza de Ratón » Progreso upload en AJAX

  23. Byron Whiteson (BB)

    Thanks Joshua, although it took me 2 days to get going this is what i found:

    1) no patch is need when you have the latest PHP (5.2)
    2) no extra security configurations are needed in http.conf (apache)
    3) php.ini just needs the upload_tmp_dir= set
    4) requires used in demoserver.php and Ajax.php need full proper paths if you are not using pear ..ie
    require_once(getenv(‘DOCUMENT_ROOT’) . ‘/temp/HTML/AJAX/Server.php’);
    5) REQUIRED !!!! moving files after temp:(mine disappeared before i could see them)

    $uploaddir = “c:/temp/”;
    $uploaddir.= $_FILES[‘upload’][‘tmp_name’];

    //Copy the file to some permanent location
    if(move_uploaded_file($_FILES[“upload”][“tmp_name”], $uploaddir))
    {
    echo “I did some uploading”;
    }
    else
    {
    echo “There was a problem when uploding the new file “;
    print_r($_FILES);
    }
    6) $_FILES[‘upload’], the ‘upload’ specifier must match the specifier in the demo form
    7) Fan ENJOY

  24. Pingback: Cabeza de Ratón » Progreso de UpLoad de un archivo con AJAX

  25. Gajendra

    Install HTML_AJAX (pear install HTML_AJAX-alpha)
    Download PAFUPMU and install it somewhere accessible.

    Hi,
    Could these two things can be installed on a shared hosting.

    Gajendra

  26. Sailcomp

    Hello Joshua & Co.

    I’ve installed the Skript, but I have the same problem like others before and don’t know how to solve it:

    With the browser: IE7
    PHP Version: 5.2.2
    When page is loaded:
    Error: Zeile 182, Zeichen: 1, Fehler: ‘HTML_AJAX’ ist undefiniert, Code: 0
    While uploading:
    Error: Zeile 167, Zeichen: 3, Fehler: ‘UploadProgressMeterStatus’ ist undefiniert, Code: 0.

    I don’t know PEAR and AJAX really, but they should work. I think the problem is near line 166 of the UploadProgressMeter.js:
    __________
    UploadProgressMeter_remote = new UploadProgressMeterStatus(callback);
    __________
    and line 180+:
    __________
    HTML_AJAX.onError = function(err) {
    document.getElementById(‘debug’).innerHTML += HTML_AJAX_Util.varDump(err);
    }
    __________

    I really want to bring your skript to work, what could I do?

  27. Joshua Eichorn Post author

    Sailcomp: I’m guessing you have a problem with your HTML_AJAX install, it looks like the stub class and or library isn’t getting loaded properly. Loading the javascript includes by hand is a good way to check for errors.

  28. Sailcomp

    Hello Joshua

    Now it works, thank you! I have still a question 🙂 The Progressbar goes always from left to right and from right to left. What should I do, that it becomes constantly larger? I saw it like this on a demo page.

    Thank you!
    Sailcomp

  29. thenoob

    Joshua,
    I am having some slight problems with the demo, along with what I have installed on my server. This happens both in firefox 2 and internet explorer 7. When I upload a file on a dialup connection, the upload bar starts with “Connecting” then goes to “Complete”, then starts showing the progress of the upload. When the upload is larger (ie ~500kb), the bar says connecting until the download is finished, then goes to “Complete”. Is this normal? Could it be some sort of bug? It only seems to happen on slow connections.

  30. DNVC

    Wow! This AJAX progress bar is by far the best and perfect working one that i found. Im going to Integrate this on my file hosting site. Thank you very for Joshua. You Rock!

  31. Cyp

    I have a question..Why is not uploading my file? Goes to complete and the file is not in my server..

  32. pablasso

    I have the same problem than sailcomb, everything goes fine, but the bars just goes left and right until completed, instead of showing the actual progress on the file.

    I installed HTM_AJAX via Pear, is this a known issue or normal behaviour, do you know how can we fix this?

    Thanks!

  33. Joshua Eichorn Post author

    Cyp: You have to move the uploaded file at the end of the upload progress just like any other PHP upload script. There is a comment in the code showing you where to edit.

    Pablasso: Did you install a progress extension, check the article linked from the top, the comments have info on what to do for php 5.2+

  34. pablasso

    @Joshua, that was it, u couldn’t install the extension properly, because there’s no rpm or repository for php-devel 5.2+ on CentOS

    I’ll need to debug on why installing php 5.2.3 from sources didn’t included the php-dev 5.2.3 headers

    *stupid centos*

  35. abhijit

    If a file of around 2mb is selected and uploaded, the progress bar just goes to complete and it says upload complete but the process still runs on and upload progress is not shown. In case of small files, the progress bar becomes full and says upload complete, only then the bar becomes blank again and upload progress starts. Its not perfect. Is there any way of making a progress bar like Flickr’s?

  36. Kriss

    Hi Joshua. Thanks for the script!

    I have php 5.2.5 and I also get the same problem as abhijit. I have downloaded the uploadprogress -0.3.0 as you suggested and extracted xms files package and package2. I am not sure what to do with those files, and also I can not find the uploaded files, I have tried to search for them. Can you please tell me where they will be located.

    Many thanks

  37. Kriss

    Hi Joshua, I have downloaded the extension, I’m just not sure how to use it.

    I have the latest version of the code.

    Many thanks