HTML5 File Uploads with jQuery

Demo Download

Today we will be developing a small web application called Upload Center, that will allow people to upload photos from their computers by dragging and dropping them onto the browser window, possible with the new HTML5 APIs exposed by modern browsers.

The photos will have a preview and a progress bar, all of which controlled on the client side. Currently, the photos are only stored in a folder on the server, but you could improve it any way you like.

What are HTML5 File Uploads?

Uploading files using HTML5 is actually a combination of three technologies - the new File Reader API, the also new Drag & Drop API, and the good ol' AJAX (with the addition of binary data transfer). Here is a description of a HTML5 file upload process:

  1. The user drops one or more files from their file system to the browser window by dragging. Browsers that support the Drag & Drop API will fire an event, which alongside other useful information, contains a list of files that were dropped;
  2. Using the File Reader API, we read the files in the list as binary data, and store them in memory;
  3. We use the new sendAsBinary method of the XMLHttpRequest object, and send the file data to the server.

Sounds complicated? Yes, it could use some optimization. Fortunately, there are jQuery plugins that can do this for us. One of them is Filedrop, which is a wrapper around this functionality, and provides features for limiting maximum file size and specifying callback functions, which is really handy for integrating it into your web applications.

Currently file uploads work only in Firefox and Chrome, but upcoming major versions of the other browsers also include support for it. A simple fallback solution for older browsers would be to display a regular file input dialog, but we won't be doing this today, as we will be focusing our attention on using HTML5.

So lets get started!

The HTML

The markup of our Upload Center couldn't be simpler. We have a regular HTML5 document, which includes our stylesheet and script.js file, the Filedrop plugin and the jQuery library.

index.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>HTML5 File Drag and Drop Upload with jQuery and PHP | Tutorialzine Demo</title>

        <!-- Our CSS stylesheet file -->
        <link rel="stylesheet" href="assets/css/styles.css" />

        <!--[if lt IE 9]>
          <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        <![endif]-->
    </head>

    <body>

        <header>
            <h1>HTML5 File Upload with jQuery and PHP</h1>
        </header>

        <div id="dropbox">
            <span class="message">Drop images here to upload. <br /><i>(they will only be visible to you)</i></span>
        </div>

        <!-- Including The jQuery Library -->
        <script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>

        <!-- Including the HTML5 Uploader plugin -->
        <script src="assets/js/jquery.filedrop.js"></script>

        <!-- The main script file -->
        <script src="assets/js/script.js"></script>

    </body>
</html>

The only div that the Filedrop interacts with, is #dropbox. We will pass this element to the plugin, which will detect when a file is dropped on top of it. The message span is updated if there is an error condition (for example if your browser does not support one of the HTML5 APIs that this example relies on).

html5-jquery-php-upload-progress.jpg

Later, when we drop a file, our jQuery code will display a preview by adding the following markup to the page:

<div class="preview done">

    <span class="imageHolder">
        <img src="" />
        <span class="uploaded"></span>
    </span>

    <div class="progressHolder">
        <div class="progress"></div>
    </div>

</div>

This snippet contains a preview of the image (the source attribute is going to be populated with a DataURL of the picture) and a progress bar. The whole preview can have the ".done" class, which causes the ".uploaded" span to show up (it is hidden by default). This span has the green check mark as its background, and indicates the upload is complete.

Great, lets move on to our script.js file!

The jQuery Code

As all of the actual file transfer functionality is handled by the Filedrop plugin, we only need to call it and pass a few callbacks, so we can hook it to our Upload Center. We will be writing a small PHP script that handles the uploads on the server in the next section.

The first step is to write a helper function that takes a file object (a special object which is created by the web browser on file drop, and has properties like file name, path and size), and creates the markup for previewing the upload.

assets/js/script.js

var template = '<div class="preview">'+
                        '<span class="imageHolder">'+
                            '<img />'+
                            '<span class="uploaded"></span>'+
                        '</span>'+
                        '<div class="progressHolder">'+
                            '<div class="progress"></div>'+
                        '</div>'+
                    '</div>'; 

    function createImage(file){

        var preview = $(template),
            image = $('img', preview);

        var reader = new FileReader();

        image.width = 100;
        image.height = 100;

        reader.onload = function(e){

            // e.target.result holds the DataURL which
            // can be used as a source of the image:

            image.attr('src',e.target.result);
        };

        // Reading the file as a DataURL. When finished,
        // this will trigger the onload function above:
        reader.readAsDataURL(file);

        message.hide();
        preview.appendTo(dropbox);

        // Associating a preview container
        // with the file, using jQuery's $.data():

        $.data(file,preview);
    }

The template variable holds the HTML5 markup of the preview. We get the DataURL of the image (a base64 encoded representation of the image bytes) and add it as the source of the image. Everything is then appended to the dropbox container. Now we are left with calling the filedrop plugin:

assets/js/script.js

$(function(){

    var dropbox = $('#dropbox'),
        message = $('.message', dropbox);

    dropbox.filedrop({
        // The name of the $_FILES entry:
        paramname:'pic',

        maxfiles: 5,
        maxfilesize: 2, // in mb
        url: 'post_file.php',

        uploadFinished:function(i,file,response){
            $.data(file).addClass('done');
            // response is the JSON object that post_file.php returns
        },

        error: function(err, file) {
            switch(err) {
                case 'BrowserNotSupported':
                    showMessage('Your browser does not support HTML5 file uploads!');
                    break;
                case 'TooManyFiles':
                    alert('Too many files! Please select 5 at most!');
                    break;
                case 'FileTooLarge':
                    alert(file.name+' is too large! Please upload files up to 2mb.');
                    break;
                default:
                    break;
            }
        },

        // Called before each upload is started
        beforeEach: function(file){
            if(!file.type.match(/^image\//)){
                alert('Only images are allowed!');

                // Returning false will cause the
                // file to be rejected
                return false;
            }
        },

        uploadStarted:function(i, file, len){
            createImage(file);
        },

        progressUpdated: function(i, file, progress) {
            $.data(file).find('.progress').width(progress);
        }

    });

    var template = '...'; 

    function createImage(file){
        // ... see above ...
    }

    function showMessage(msg){
        message.html(msg);
    }

});

With this, every valid image file that is dropped on the #dropbox div gets uploaded to post_file.php, which you can see in the next section.

html5-jquery-php-upload-done.jpg

The PHP Code

On the PHP side of things, there is no difference between a regular form file upload and a drag and drop one. This means that you can easily provide a fallback solution to your application and reuse the same backend.

post_file.php

// If you want to ignore the uploaded files,
// set $demo_mode to true;

$demo_mode = false;
$upload_dir = 'uploads/';
$allowed_ext = array('jpg','jpeg','png','gif');

if(strtolower($_SERVER['REQUEST_METHOD']) != 'post'){
    exit_status('Error! Wrong HTTP method!');
}

if(array_key_exists('pic',$_FILES) && $_FILES['pic']['error'] == 0 ){

    $pic = $_FILES['pic'];

    if(!in_array(get_extension($pic['name']),$allowed_ext)){
        exit_status('Only '.implode(',',$allowed_ext).' files are allowed!');
    }   

    if($demo_mode){

        // File uploads are ignored. We only log them.

        $line = implode('       ', array( date('r'), $_SERVER['REMOTE_ADDR'], $pic['size'], $pic['name']));
        file_put_contents('log.txt', $line.PHP_EOL, FILE_APPEND);

        exit_status('Uploads are ignored in demo mode.');
    }

    // Move the uploaded file from the temporary
    // directory to the uploads folder:

    if(move_uploaded_file($pic['tmp_name'], $upload_dir.$pic['name'])){
        exit_status('File was uploaded successfuly!');
    }

}

exit_status('Something went wrong with your upload!');

// Helper functions

function exit_status($str){
    echo json_encode(array('status'=>$str));
    exit;
}

function get_extension($file_name){
    $ext = explode('.', $file_name);
    $ext = array_pop($ext);
    return strtolower($ext);
}

The script runs some checks on the HTTP method that was used to request the page and the validity of the file extension. Demo mode is mainly for demo.tutorialzine.com, where I don't want to store any file uploads (if you don't call move_uploaded_file in your script, the file is deleted automatically at the end of the request).

Now lets make it pretty!

The CSS Styles

I left out the parts of the stylesheet that are not directly related to the uploads. You can see everything in styles.css.

assets/css/styles.css

/*-------------------------
    Dropbox Element
--------------------------*/

#dropbox{
    background:url('../img/background_tile_3.jpg');

    border-radius:3px;
    position: relative;
    margin:80px auto 90px;
    min-height: 290px;
    overflow: hidden;
    padding-bottom: 40px;
    width: 990px;

    box-shadow:0 0 4px rgba(0,0,0,0.3) inset,0 -3px 2px rgba(0,0,0,0.1);
}

#dropbox .message{
    font-size: 11px;
    text-align: center;
    padding-top:160px;
    display: block;
}

#dropbox .message i{
    color:#ccc;
    font-size:10px;
}

#dropbox:before{
    border-radius:3px 3px 0 0;
}

/*-------------------------
    Image Previews
--------------------------*/

#dropbox .preview{
    width:245px;
    height: 215px;
    float:left;
    margin: 55px 0 0 60px;
    position: relative;
    text-align: center;
}

#dropbox .preview img{
    max-width: 240px;
    max-height:180px;
    border:3px solid #fff;
    display: block;

    box-shadow:0 0 2px #000;
}

#dropbox .imageHolder{
    display: inline-block;
    position:relative;
}

#dropbox .uploaded{
    position: absolute;
    top:0;
    left:0;
    height:100%;
    width:100%;
    background: url('../img/done.png') no-repeat center center rgba(255,255,255,0.5);
    display: none;
}

#dropbox .preview.done .uploaded{
    display: block;
}

/*-------------------------
    Progress Bars
--------------------------*/

#dropbox .progressHolder{
    position: absolute;
    background-color:#252f38;
    height:12px;
    width:100%;
    left:0;
    bottom: 0;

    box-shadow:0 0 2px #000;
}

#dropbox .progress{
    background-color:#2586d0;
    position: absolute;
    height:100%;
    left:0;
    width:0;

    box-shadow: 0 0 1px rgba(255, 255, 255, 0.4) inset;

    -moz-transition:0.25s;
    -webkit-transition:0.25s;
    -o-transition:0.25s;
    transition:0.25s;
}

#dropbox .preview.done .progress{
    width:100% !important;
}

The .progress div is positioned absolutely. Changing its width (in percent) makes for a natural progress indicator. Throw in a 0.25 transition, and you have animated increments which would be a bit tricky to do with jQuery alone.

With this our HTML5 Upload Center is complete!

We're done!

You can use this as a starting point for a file upload service, HTML5 gallery, file manager or your app's new admin panel. Add your thoughts or suggestions in the comments.

Bootstrap Studio

The revolutionary web design tool for creating responsive websites and apps.

Learn more

Related Articles