Tracking Upload Progress with PHP and JavaScript

A problem that has plagued web developers for years is how to add real-time information to their applications, such as a progress bar for file uploads. Users are impatient; they don’t want to sit and wait while the browser is doing something and wonder whether it has frozen or if they have a slow connection. Providing a progress indicator gives users useful information and lets them know exactly what’s going on.

At first thought, you might think accomplishing this can be done easily by first obtaining the file’s size from the user’s computer and then performing some simple calculations against the directory on the server where the file is being uploaded to. On second thought, you’d find things aren’t quite that simple.

JavaScript can access a file’s name, type, and even the width and height of a local image, but it wasn’t until HTML5 that it could access a file’s size. Unfortunately, HTML5 still isn’t a completed standard yet and isn’t uniformly supported across all browsers. An alternate solution is to rely on a Flash, Java, or ActiveX plugin; no thanks, I’ll pass. Yet another solution is to install the Alternative PHP Cache extension, but that may not be available depending on your hosting environment and it seems like overkill for such a small task such as this.

It would seem as though all the options are fraught with nuisances and the task has quickly become a headache. But in the words of Yoda, “No… There is another.”

One of the many reasons I love PHP is that it makes seemingly difficult tasks easy. As of PHP 5.4, they’ve done it again with a new set of configuration directives, session.upload_progress.

In this article I’ll show you how this feature can be used to create a simple upload progress bar without any external libraries or browser dependencies. I’ll first discuss how it works, and then I’ll walk you through creating the four files needed to accomplish the task (an upload form, some JavaScript, a little CSS, and a file to return the status of the upload).

Session Upload Progress

Besides the usual requirements to allow file uploads, there are two more to track the progress. The session.upload_progress.enabled directive must be enabled and there must be a hidden field in your web form with the name specified by the session.upload_progress.name directive. When session.upload_progress.enabled is true (as it is by default in PHP 5.4 and presumably beyond) and $_POST[session.upload_progress.name] is sent during an upload, information about the file transfer is made available in the $_SESSION superglobal array.

The print_r() output of the $_SESSION array will look similar to the following during a file transfer:

Array
(
    [upload_progress_myForm] => Array
        (
            [start_time] => 1323733740
            [content_length] => 721127769
            [bytes_processed] => 263178326
            [done] =>
            [files] => Array
                (
                    [0] => Array
                        (
                            [field_name] => userfile
                            [name] => ubuntu-10.04.3-desktop-i386.iso
                            [tmp_name] =>
                            [error] => 0
                            [done] =>
                            [start_time] => 1323733740
                            [bytes_processed] => 263178026
                        )
                )
        )
)

When you are developing either locally or on a fast network and upload small files, you won’t be able to visually observe the progress because the transfer happens so fast. In this case, you might want to try transferring a large file. Make sure the settings in your php.ini file allow large uploads, specifically the post_max_size and upload_max_filesize directives, and then verify they are sane values when you go to production.

Create the Form

The first file I’ll present is the upload form. Just to keep things as simple as possible, the example will post to itself and only handle one file upload at a time. Additionally, I won’t bother saving the file after it has been uploaded.

Here’s the code for form.php:

01 <?php
02 if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_FILES["userfile"])) {
03     // move_uploaded_file()
04 }
05 ?>
06 <html>
07  <head>
08   <title>File Upload Progress Bar</title>
09   <link rel="stylesheet" type="text/css" href="style.css">
10  </head>
11  <body>
12   <div id="bar_blank">
13    <div id="bar_color"></div>
14   </div>
15   <div id="status"></div>
16   <form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="POST"
17    id="myForm" enctype="multipart/form-data" target="hidden_iframe">
18    <input type="hidden" value="myForm"
19     name="<?php echo ini_get("session.upload_progress.name"); ?>">
20    <input type="file" name="userfile"><br>
21    <input type="submit" value="Start Upload">
22   </form>
23   
24   <script type="text/javascript" src="script.js"></script>
25  </body>
26 </html>

In the example the code to actually process the file has been omitted to keep things simple. If you’re interested in what such code should look like, check out the article File Uploads with PHP by Timothy Boronczyk.

After the head section which provides the page’s title and includes the stylesheet, you’ll notice a small collection of div elements. The div with the ID “bar_blank” is the container for the progress bar. The div with the ID “bar_color” will be dynamically updated as the file upload progresses. The “status” div will display the numeric value of the percent uploaded.

The form is set to submit to the same URL and its target attribute points to a hidden iframe element. Submitting a form to a hidden frame allows you to keep the visitor on the same page while the work is being done in the background. In fact, this is a common practice when doing “Ajax file uploads” since it isn’t possible to send the contents of a file directly using JavaScript’s XmlHttpRequest object.

Within the form, the special hidden field needed to populate the $_SESSION array appears, followed by a file upload input and submit button. Submitting the form will trigger a JavaScript function named startUpload() which will be defined by the included JavaScript file.

At the bottom of the page is the hidden frame to which the form will post and the import of the script.js file.

Add Some Style

The next file, style.css, is pretty straight-forward. I’ve defined the size of the progress bar container and given it a 1px black border, the color of the progress bar as it’s loading, and both the iframe and the progress bar are hidden.

01 #bar_blank {
02   border: solid 1px #000;
03   height: 20px;
04   width: 300px;
05 }
06
07 #bar_color {
08   background-color: #006666;
09   height: 20px;
10   width: 0px;
11 }
12
13 #bar_blank, #hidden_iframe {
14   display: none;
15 }

Client-Side Functionality

The script.js file is the largest of the group of files. It contains six functions which I will discuss below. Many people like to use jQuery to provide some of the functionality here, and you are certainly free to do so if you wish, but I personally prefer the old-school approach. Similar to how the Japanese place a higher value on hand crafted goods, I just feel more passionate about the code if it is my own.

01 function toggleBarVisibility() {
02     var e = document.getElementById("bar_blank");
03     e.style.display = (e.style.display == "block") ? "none" : "block";
04 }
05
06 function createRequestObject() {
07     var http;
08     if (navigator.appName == "Microsoft Internet Explorer") {
09         http = new ActiveXObject("Microsoft.XMLHTTP");
10     }
11     else {
12         http = new XMLHttpRequest();
13     }
14     return http;
15 }
16
17 function sendRequest() {
18     var http = createRequestObject();
19     http.open("GET", "progress.php");
20     http.onreadystatechange = function () { handleResponse(http); };
21     http.send(null);
22 }
23
24 function handleResponse(http) {
25     var response;
26     if (http.readyState == 4) {
27         response = http.responseText;
28         document.getElementById("bar_color").style.width = response + "%";
29         document.getElementById("status").innerHTML = response + "%";
30
31         if (response < 100) {
32             setTimeout("sendRequest()", 1000);
33         }
34         else {
35             toggleBarVisibility();
36             document.getElementById("status").innerHTML = "Done.";
37         }
38     }
39 }
40
41 function startUpload() {
42     toggleBarVisibility();
43     setTimeout("sendRequest()", 1000);
44 }
45
46 (function () {
47     document.getElementById("myForm").onsubmit = startUpload;
48 })();

The toggleBarVisibility() function sets an appropriate style on the “bar_blank” div to show or hide the progress bar as needed. Initially it starts out hidden, but will be displayed once an upload starts and hidden again when an upload finishes.

The createRequestObject() function creates an XMLHttpRequest or ActiveXObject object based on the user’s browser. This is probably the function most people would look to jQuery or some other JavaScript framework to provide.

The sendRequest() function requests the progress.php file with a GET request, and then invokes the handleResponse() function to handle the returned data.

The handleResponse() function handles the response from progress.php which will be a number between 1-100 depending on the file upload progress. I also update the “status” div with the appropriate value. If the current percent is below 100 then I call JavaScript’s native setTimeout() function to send another request for an update after 1 second (you may want to adjust this value as appropriate), otherwise I hide the progress bar again and set the status to “Done.”

The startUpload() function makes the upload bar visible and sends a request for an update after a delay of 1 second. This small delay is needed in order to give the upload time to start.

The final function is a self-executing anonymous function which registers startUpload() with the form’s submit event.

Real-Time Progress

The last file that brings everything together is the progress.php file:

01 <?php
02 session_start();
03
04 $key = ini_get("session.upload_progress.prefix") . "myForm";
05 if (!empty($_SESSION[$key])) {
06     $current = $_SESSION[$key]["bytes_processed"];
07     $total = $_SESSION[$key]["content_length"];
08     echo $current < $total ? ceil($current / $total * 100) : 100;
09 }
10 else {
11     echo 100;
12 }

The script performs some simple math on the current number of bytes transferred divided by the total file size, multiplied by 100 and rounded off to give a percent.

Information about the transfer is keyed with a concatenation of the session.upload_progress.prefix directive’s value and the hidden session.upload_progress.name field’s value. Because my form passed “myForm”, the session’s key is determined with ini_get("session.upload_progress.prefix") . "myForm".

Here’s a screenshot of the progress bar in action:

upload progress bar

Fine-Tuning the Behavior

PHP provides some additional directives to help fine-tune the behavior of session uploads you should be aware of. For example, session.upload_progress.cleanup, which is set to 1 by default, cleans up the extra session data immediately after the upload has finished. You need to be careful to avoid a potential race condition.

Take a look again at the code in progress.php and you’ll notice that I check to see if $_SESSION[$key] is empty or not before proceeding. My JavaScript functions fire off every second as long as the result returned from progress.php is less than 100. If session.upload_progress.cleanup is enabled and my script fetches 99% of the upload and a 1/2-second later the upload finishes, $_SESSION[$key] would not exist for the next check. If I didn’t take that into consideration then my JavaScript function might keep firing, even after the upload finished.

Another two directives are session.upload_progress.freq and session.upload_progress.min_freq which both determine how often the session should be updated. The value of freq can be given in either bytes (i.e., 100) or the percentage of total bytes (i.e., 2%). The value of min_freq is given in seconds and indicates the minimum number of seconds between updates. Obviously if min_freq was set to update every 1 second, it would be pointless for your JavaScript to check every 100 milliseconds.

Summary

You should now have a solid grasp on how to create a progress bar for file uploads using the session upload progress feature. Moving forward, I encourage you to experiment with uploading multiple files, giving the option to cancel an upload in progress using $_SESSION[$key]["cancel_upload"], or whatever other ideas your mind can muster. Please share in the comments your experiences and improvements you’ve made for others to benefit from and enjoy as well!

Referenced Link ╠►

Advertisements
By Rz Rasel Posted in Php

46 comments on “Tracking Upload Progress with PHP and JavaScript

  1. Nice post. I was checking constantly this blog and I’m impressed! Very useful info particularly the last part 🙂 I care for such information much. I was looking for this particular info for a long time. Thank you and good luck.

  2. You made some decent points there. I looked on the internet
    for more info about the issue and found most individuals
    will go along with your views on this website.

  3. I am not sure where you are getting your info, but good topic.

    I needs to spend some time learning more or understanding more.
    Thanks for great information I was looking for this info for my mission.

  4. Hello There. I found your blog using msn. This is
    a really well written article. I will be sure to bookmark it and return to read more of your useful information.
    Thanks for the post. I’ll certainly comeback.

  5. Awesome website you have here but I was wanting to know if you knew of any forums that cover the same topics discussed in this article?
    I’d really love to be a part of group where I can get feed-back from other experienced individuals that share the same interest. If you have any suggestions, please let me know. Thank you!

  6. It’s the best time to make some plans for the future and it is time to be happy. I have read this post and if I could I want to suggest you some interesting things or suggestions. Maybe you can write next articles referring to this article. I desire to read more things about it!

  7. Pingback: low vitamin d symptoms

  8. I like the helpful information you provide in your articles.

    I will bookmark your weblog and check again here frequently.

    I’m quite sure I’ll learn plenty of new stuff right here!
    Good luck for the next!

  9. Pingback: URL

  10. Gday! My name is Gavin Willick and I just wanted to say your blogging site rocks! It really is funny simply because I use to have a blogging site that nearly had an identical url: https://arzrasel.wordpress.com/2012/07/25/tracking-upload-progress-with-php-and-javascript/ mine was only a few letters different. In any case, I’m a big admirer of your blog site and if you at any time would like a guest article please make sure to email me personally at: Steakley806@gmail.com. I love writing!

  11. Nice to be visiting your blog once more, it continues to be months for me. Nicely this post that ive been waited for so lengthy. I want this article to total my assignment in the university, and it has same topic together with your post. Thanks, terrific share. 373205

  12. Good day! This post could not be written any better! Reading this post reminds me of my previous room mate! He always kept chatting about this. I will forward this write-up to him. Fairly certain he will have a good read. Thanks for sharing! 976823

  13. I dont feel Ive scan anything like this before. So excellent to locate somebody with some original thoughts on this topic. thank for starting this up. This site is something that is necessary on the internet, someone with a bit originality. Excellent job for bringing something new towards the internet! 774610

  14. for however one more wonderful informative post, Im a loyal reader to this weblog and I cant stress enough how considerably valuable data Ive learned from reading your content material. I genuinely appreciate all of the hard function you put into this wonderful weblog. 795959

  15. Attractive section of content. I just stumbled upon your weblog and in accession capital to assert that I acquire in fact enjoyed account your blog posts. Anyway I will be subscribing to your feeds and even I achievement you access consistently fast.

  16. Aw, this became an really good post. In thought I would like to devote writing such as this moreover – taking time and actual effort to make a quite very good article but exactly what do I say I procrastinate alot and by no indicates uncover a strategy to get something completed. 124203

  17. You are my inspiration, I possess few blogs and infrequently run out from post :). “He who controls the past commands the future. He who commands the future conquers the past.” by George Orwell.

  18. I do agree with all the concepts you’ve presented to your post. They’re really convincing and can certainly work. Still, the posts are very quick for novices. Could you please lengthen them a little from subsequent time? Thank you for the post.

  19. One other issue is that if you are in a circumstance where you do not have a cosigner then you may actually want to try to wear out all of your financial aid options. You can find many funds and other scholarship grants that will provide you with finances to aid with institution expenses. Thanks a lot for the post.

  20. I will right away seize your rss feed as I can not find your email subscription hyperlink or newsletter service. Do you’ve any? Kindly permit me realize in order that I could subscribe. Thanks.

  21. Hello! I would wish to supply a large thumbs up for your outstanding info you could have here about this post. Ill be coming back to your blog website for further soon. 400331

  22. Somebody necessarily assist to make seriously articles I would state. That is the first time I frequented your website page and so far? I surprised with the analysis you made to create this actual publish amazing. Great activity!

  23. Somebody essentially lend a hand to make critically posts I’d state. This is the very first time I frequented your web page and up to now? I surprised with the research you made to create this actual submit incredible. Wonderful process!

  24. I am really impressed with your writing skills and also with the layout on your blog. Is this a paid theme or did you customize it yourself? Anyway keep up the excellent quality writing, it’s rare to see a nice blog like this one today

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s