UltraMega Tech.
11Oct/1084

Create an Upload Progress Bar With PHP and jQuery

When it comes to uploading files, users expect visual feedback, usually in the form of a progress bar. The problem is that PHP doesn’t offer a way to track file uploads in progress by default. Fortunately, there is an extension that enables this functionality and this tutorial will show how it can be combined with jQuery UI to create a progress bar.

Here is a demo of the effect we will be building in this tutorial:

Introduction and Setup

In this tutorial, we will be making use of the jQuery UI Progressbar widget and the uploadprogress extension for PHP together to create a visual indicator of file upload progress.

Before we start, you should get your file structure set up. You'll need three empty PHP files: index.php, upload.php and getprogress.php. You'll also need a directory to hold the uploaded files. Here is what the file structure should look like:

Directory Setup

If you are using a local copy of jQuery UI, make sure it includes the Progressbar widget.

Step 1: Install The uploadprogress Extension

The first thing we need to do is make sure the required extension, uploadprogress, is installed. Since this is a PECL extension, you use the standard installation procedure for PECL extensions, which is similar to PEAR.

Check For The Extension

The easiest way to find out if the extension is available is to call the phpinfo() function. Create a PHP file on your server containing:

<?php phpinfo(); ?>

and visit the page with a browser. Search for a section titled "uploadprogress", which will look something like this:

uploadprogress Section

If you find it, congratulations, you already have the required extension! If not, read on.

Be sure to remove the phpinfo file when you are finished!

Get The Extension

The easiest way to get the extension is to run the following command as a root or administrative user:

pecl install uploadprogress

Assuming there are no errors, this will download and compile the extension. If you get a "command not found" error, you'll need to install PECL using the method appropriate for your distribution and try again.

Here is what the last part of the output should look like if the command is successful:

PECL Output

Load The Extension

Now you'll just need to load the extension, which usually means adding a line like the one below to your php.ini file and restarting the web server.

Linux:

extension=uploadprogress.so

Windows:

extension=uploadprogress.dll

Some installations have individual ini files for each extension, in which you'd put the above line. For example, you may have to create a file called uploadprogress.ini in /etc/php.d and place the extension directive in there instead of within the main php.ini.

Be sure to restart your web server for the changes to take effect.

Step 2: Create The Upload Form

In order to have an upload to track, we need a form to accept a file upload. This part is fairly basic, but there are a few important things we need to do to make tracking possible. We'll also need to add a place for a progress bar widget.

First, we need to generate a unique string. If the user decides to upload a file, this will be used to identify and track the upload. This should go right at the top of the index.php file:

<?php
// Generate random string for our upload identifier
$uid = md5(uniqid(mt_rand()));
?>

We also need to include jQuery and jQuery UI to power our front-end. Here we include the libraries along with the default jQuery UI theme from the Google CDN:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Upload Something</title>
        <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/themes/start/jquery-ui.css" rel="stylesheet" />
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/jquery-ui.min.js"></script>
    </head>
    <body>
 
    </body>
</html>

Now it's time to create the form in the body:

        <form id="upload-form"
              method="post"
              action="upload.php"
              enctype="multipart/form-data"
              target="upload-frame" >
            <input type="hidden"
                   id="uid"
                   name="UPLOAD_IDENTIFIER"
                   value="<?php echo $uid; ?>" >
            <input type="file" name="file">
            <input type="submit" name="submit" value="Upload!">
        </form>

This will show as a basic file selection field with an "Upload" button. However, there are several things to note in the markup that makes everything functional. In our form tag, we have some important attributes:

  • method=post: By default, forms use GET so we want to make sure files are sent via POST
  • action=upload.php: This specifies the script that will accept the upload from the form
  • enctype=multipart/form-data: This is required in order to handle file uploads
  • target=upload-frame: This will be an iframe that will accept the form submission in the background, while the main page can still be manipulated

There is also a hidden field that specifies our upload identifier. The uploadprogress extension looks for a field called UPLOAD_IDENTIFIER to decide whether to track the upload, and uses its value as the identifier. This field must come before the file input!

With the form in place we need to add a div for the progress bar, and the iframe I mentioned earlier:

        <div id="progress-bar"></div>
        <iframe id="upload-frame" name="upload-frame"></iframe>

We'll use some CSS in the head to hide these from view:

        <style>
            #progress-bar, #upload-frame {
                display: none;
            }
        </style>

Here's what the entire page should look like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<?php
// Generate random string for our upload identifier
$uid = md5(uniqid(mt_rand()));
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Upload Something</title>
        <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/themes/start/jquery-ui.css" rel="stylesheet" />
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/jquery-ui.min.js"></script>
        <style>
            #progress-bar, #upload-frame {
                display: none;
            }
        </style>
        <script>
            // JavaScript here
        </script>
    </head>
    <body>
        <form id="upload-form"
              method="post"
              action="upload.php"
              enctype="multipart/form-data"
              target="upload-frame" >
            <input type="hidden"
                   id="uid"
                   name="UPLOAD_IDENTIFIER"
                   value="<?php echo $uid; ?>" >
            <input type="file" name="file">
            <input type="submit" name="submit" value="Upload!">
        </form>
        <div id="progress-bar"></div>
        <iframe id="upload-frame" name="upload-frame"></iframe>
    </body>
</html>

Note: we will be placing our JavaScript between the empty script tags later.

Step 3: Create The PHP Back-End

Our PHP back-end will consist of two parts, the upload processing script and the progress fetcher. The upload processor will accept the file from the form and the progress fetcher will be called via AJAX to get progress status updates.

Closing PHP tags are only necessary to switch to plain output, and can be omitted in many cases.

Let's get the upload processing script out of the way. Place this in upload.php:

1
2
3
4
5
<?php
if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
    $path = './uploads/' . basename($_FILES['file']['name']);
    move_uploaded_file($_FILES['file']['tmp_name'], $path);
}

This is your basic file upload skeleton, which simply places the file into an upload directory. In real life you'll want to add some sanity checks here, but that is beyond the scope of this tutorial.

This next part is where things get interesting. Here is the script that will output the current percentage of the file upload, which will be used to update the progress bar. Place this in getprogress.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
 
if (isset($_GET['uid'])) {
 
    // Fetch the upload progress data
    $status = uploadprogress_get_info($_GET['uid']);
 
    if ($status) {
 
        // Calculate the current percentage
        echo round($status['bytes_uploaded']/$status['bytes_total']*100);
 
    }
    else {
 
        // If there is no data, assume it's done
        echo 100;
 
    }
}

This simple script calls the uploadprogress_get_info function provided by the uploadprogress extension. This function takes an identifier as a parameter and returns an array of upload status information, or null if none is found. We're only interested in the bytes_uploaded and bytes_total array items so we can calculate the percentage.

If the function returns null, it means one of three things: the upload hasn't started, the upload is complete, or the upload doesn't exist. This script simply assumes the upload is complete and prints 100. It is up to our JavaScript front-end to determine what is really going on.

Step 4: Create the JavaScript Front-End

With all the important pieces in place, we will bring everything together with JavaScript. The front-end will be responsible for creating the progress bar and querying our back-end for status updates.

Here is the basic skeleton of our script:

(function ($) {
 
    // We'll use this to cache the progress bar node
    var pbar;
 
    // This flag determines if the upload has started
    var started = false;
 
}(jQuery));

Our script is contained within this self-invoking function with jQuery passed as a parameter named $. This ensures that the $ jQuery alias is available within our script. We have also declared two variables, one to reference the progress bar element (pbar) and one to determine whether we have started uploading (started).

We need to start the progress bar when the form is submitted, so we will attach a function to the form's submit event:

    $(function () {
 
        // Start progress tracking when the form is submitted
        $('#upload-form').submit(function() {
 
            // Hide the form
            $('#upload-form').hide();
 
            // Cache the progress bar
            pbar = $('#progress-bar');
 
            // Show the progress bar
            // Initialize the jQuery UI plugin
            pbar.show().progressbar();
 
        });
 
    });

$(function () { }) is shorthand for $(document).ready(function () { })

The entire event code is wrapped in a function passed to jQuery, which is equivalent to attaching it to the document ready event. The submit event handler on our form does four things so far:

  1. Hides the form
  2. Saves the reference to the progress bar element to the pbar variable
  3. Makes the progress bar visible
  4. Attaches a jQuery UI Progressbar widget the the progress bar div

Still in the submit event function, we need to attach an event to the iframe:

        $('#upload-form').submit(function() {
 
            // ...
 
            // We know the upload is complete when the frame loads
            $('#upload-frame').load(function () {
 
                // This is to prevent infinite loop
                // in case the upload is too fast
                started = true;
 
                // Do whatever you want when upload is complete
                alert('Upload Complete!');
 
            });
 
        });

Here, we have attached a function to the iframe's load event. The load event is fired when an item has fully loaded, which in this case is when the page within the frame is loaded. Since the iframe is where the form is being submitted, this happens when the upload is complete.

When the load event fires we first set the started flag to true, since we know the upload must have started if it is finished. This is to prevent an infinite loop in case the upload completes before we can start tracking.

In the load event, we also trigger any end actions we want to perform. In this example we simply trigger an alert, but you can do whatever you want here.

The last part of our submit function is where the tracking begins:

        $('#upload-form').submit(function() {
 
            // ...
 
            // Start updating progress after a 1 second delay
            setTimeout(function () {
 
                // We pass the upload identifier to our function
                updateProgress($('#uid').val());
 
            }, 1000);
 
        });

Here, we have created a one second timeout which will call our (yet to be created) function named updateProgress, to which we pass the value of the upload identifier field. The delay gives the form time to begin sending data before we ask for updates about that data.

Now we must create that updateProgress function, which mostly consists of an AJAX request:

    function updateProgress(id) {
 
        var time = new Date().getTime();
 
        // Make a GET request to the server
        // Pass our upload identifier as a parameter
        // Also pass current time to prevent caching
        $.get('getprogress.php', { uid: id, t: time }, function (data) {
 
        });
 
    }

We are making a GET request to our back-end getprogress.php script, passing two parameters. The first is the upload identifier, uid, which is expected by our back-end. The second is the current timestamp, which simply makes the URL unique to prevent caching. I've found that this is the best way to prevent caching, since cache control headers aren't always reliable.

Since the back-end returns a percentage as an integer, we parse that data and assign it to a progress variable.

        $.get('getprogress.php', { uid: id, t: time }, function (data) {
 
            // Get the output as an integer
            var progress = parseInt(data, 10);
 
        });

This next part of the callback is where we create the loop:

        $.get('getprogress.php', { uid: id, t: time }, function (data) {
 
            // ...
 
            if (progress < 100 || !started) {
 
                // Determine if upload has started
                started = progress < 100;
 
                // If we aren't done or started, update again
                updateProgress(id);
 
            }
 
        });

If the upload progress is not 100% or we haven't started uploading, we will call the updateProgress function again. We also check if the upload has started and set the started flag appropriately, which is true as soon as the value is not 100. Now updateProgress will repeat until the upload is complete.

The last part of our callback is where we actually update the progress bar widget. Note the use of the && operator to make sure the code only runs if started is true.

        $.get('getprogress.php', { uid: id, t: time }, function (data) {
 
            // ...
 
            // Update the progress bar percentage
            // But only if we have started
            started && pbar.progressbar('value', progress);
 
        });

The Final Result

If everything is done correctly, your result should behave like this:

Below is the complete code.

upload.php (example; not production quality)

1
2
3
4
5
<?php
if ($_FILES['file']['error'] === UPLOAD_ERR_OK) {
    $path = './uploads/' . basename($_FILES['file']['name']);
    move_uploaded_file($_FILES['file']['tmp_name'], $path);
}

getprogress.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
 
if (isset($_GET['uid'])) {
 
    // Fetch the upload progress data
    $status = uploadprogress_get_info($_GET['uid']);
 
    if ($status) {
 
        // Calculate the current percentage
        echo round($status['bytes_uploaded']/$status['bytes_total']*100);
 
    }
    else {
 
        // If there is no data, assume it's done
        echo 100;
 
    }
}

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
<?php
// Generate random string for our upload identifier
$uid = md5(uniqid(mt_rand()));
?>
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Upload Something</title>
        <link href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/themes/start/jquery-ui.css" rel="stylesheet" />
        <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
        <script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.5/jquery-ui.min.js"></script>
        <style>
            #progress-bar, #upload-frame {
                display: none;
            }
        </style>
        <script>
            (function ($) {
 
                // We'll use this to cache the progress bar node
                var pbar;
 
                // This flag determines if the upload has started
                var started = false;
 
                $(function () {
 
                    // Start progress tracking when the form is submitted
                    $('#upload-form').submit(function() {
 
                        // Hide the form
                        $('#upload-form').hide();
 
                        // Cache the progress bar
                        pbar = $('#progress-bar');
 
                        // Show the progress bar
                        // Initialize the jQuery UI plugin
                        pbar.show().progressbar();
 
                        // We know the upload is complete when the frame loads
                        $('#upload-frame').load(function () {
 
                            // This is to prevent infinite loop
                            // in case the upload is too fast
                            started = true;
 
                            // Do whatever you want when upload is complete
                            alert('Upload Complete!');
 
                        });
 
                        // Start updating progress after a 1 second delay
                        setTimeout(function () {
 
                            // We pass the upload identifier to our function
                            updateProgress($('#uid').val());
 
                        }, 1000);
 
                    });
 
                });
 
                function updateProgress(id) {
 
                    var time = new Date().getTime();
 
                    // Make a GET request to the server
                    // Pass our upload identifier as a parameter
                    // Also pass current time to prevent caching
                    $.get('getprogress.php', { uid: id, t: time }, function (data) {
 
                        // Get the output as an integer
                        var progress = parseInt(data, 10);
 
                        if (progress < 100 || !started) {
 
                            // Determine if upload has started
                            started = progress < 100;
 
                            // If we aren't done or started, update again
                            updateProgress(id);
 
                        }
 
                        // Update the progress bar percentage
                        // But only if we have started
                        started && pbar.progressbar('value', progress);
 
                    });
 
                }
 
            }(jQuery));
        </script>
    </head>
    <body>
        <form method="post" action="upload.php" enctype="multipart/form-data" id="upload-form" target="upload-frame">
            <input type="hidden" id="uid" name="UPLOAD_IDENTIFIER" value="<?php echo $uid; ?>">
            <input type="file" name="file">
            <input type="submit" name="submit" value="Upload!">
        </form>
        <div id="progress-bar"></div>
        <iframe id="upload-frame" name="upload-frame"></iframe>
    </body>
</html>

Conclusion

This tutorial describes one method of creating an upload progress bar. Unlike other methods, this one relies very little on the client since no plugins are involved. The only requirement for the progress bar to display is JavaScript. Most of the work is done on the server side.

I hope you find this technique useful in one of your projects. This can be easily expanded by displaying more data from the uploadprogress extension, such as transfer speed and estimated time. If you have any questions or comments, please write a comment below.

Posted by Steve

Comments (84) Trackbacks (13)
  1. Hi thanks for this tutorial. It helped me a lot !

  2. Hi . Tank you very much for this tutorial. But in IE 8 progress bar doesn’t work and an error sign at the left bottom of the screen.. What may be problem and how can i solve.

    Thank you in advance.

  3. Hi,

    thanks for that easy tut! Even I with only a very little bit of skills (better to say NO SKILLS) in php could follow it.

    I installed pecl and then get uploadprogress and noticed it in php.ini and made a extra file in conf.d “uploadprogress.ini” as it is required and you gave the hint.

    The Upload works – the form is showing up, I can submit a file and the progressbar moves depending on the filesize.

    BUT:
    I can’t find the uploaded file. It is nowhere.

    I tried to:
    - change the name of the $path var (cause I really thought it maybe collides with the PATH enviromental Constant (very little bit of skills in php, you know ;-) )
    - give the absolute path to “uploads”-directory
    - work with the example for multiple fileupload from php.net

    Nothing works.

    My Questions:
    - what should I do to debug the move_uploaded_file function (maybe that fails??)?
    - Have you any idea what I could try to get it work?

    Thanks in advance!

    Michael

    • If you are on a Linux server, you’ll need to be sure the upload directory is writable to the user the web server is running as. Try chmod 777 the directory and see if it works.

  4. Hey Steve,

    that’s it! I assumed that 755 for the dir is enough. Now it works…THANKS A LOT for that and the quick reply!

    Greets

    Michael

    • Good. Just remember to check the uploaded file before moving it if you plan to use this. Make sure to only accept the file types you want or else someone could upload a php or other executable file and run any code they want.

  5. ok, thanks, I will do that!

  6. @Steve
    Thanks dude for sharing this.
    GOD Bless you for not being selfish.

    By the way it works.

  7. Hello. I have an error with the jquery code in the IE 8 . How can i solve the problem.

    This is the error ::

    Row: 123
    Character: 183
    Code: 0
    URI: http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js

    If you help me, I would be very happy.

  8. Hi thanks for this useful simple code,

    I have a problem with image resizing with this code sample. I tried to upload with resizing image ist ok but progresbar get 100% before image uploading. I use imagecreatefromjpeg() function and image destroy() is it effect uploadframe or something else.

    $path = ‘./ProfilResim/’ . basename($_FILES['file']['name']);
    move_uploaded_file($_FILES['file']['tmp_name'], $path);

    i delete this tow step and put my own resize code

    second problem is how can i checked if file is image or not

    $type= $_FILES['file']['type'];
    if (($tip == ‘image/jpeg’))
    i used like this but else step not show anyting

    thanks for your regards

  9. Thank you, this is awesome! I tried it and it worked so simply!

    However, in integrating this into my existing application, I get “NULL” from the uploadprogress_get_info() function. Why?

    In my application page, my image upload form is created dynamically. But at the beginning of my page (and before the user hits a button that dynamically creates an image upload form), I am using this line:

    <input type='hidden' name='upload_uid' id='uid' value='

    Is this the problem? Is there a specific time or place this hidden input should be present?

  10. well in the above line, part of my code got filtered out in the comment.
    basically the value of the hidden input up there is simple $uid as per this tutorial.

  11. Any suggestions on getting this to work with Windows Server 2008 and IIS 7.5 …. i have php v. 5.3.24 and it all works fine except for the progress bar does not update. it does show up when submitted and the prompt alerts when completed but the bar does not move from 0%

  12. Hi, thanks for this work it helped me so much, i´m not a programmer yet, i´m trying to learn php from the beginning but my boss asked me to a webpage with upload bar and i see this work and now i got it.

    I have a doubt, if i want to add a upload percentage how can i do it? Could you help me?

    Regards.


Leave a comment