Drag and Drop File Upload With Google Gears

Blog Projects
javascript gears php jquery

Google Gears is an extension which adds new features to you browser. It lets your browser to interact with the desktop. You can store data locally to an SQLite based database. Browser will also have a worker pool for running JavaScript code on the background.

Update 20091007: I updated the tutorial and demo to support dragging and dropping multiple files for uploading simultaneously.

Below is how you can do basic drag and drop file upload. Gears, PHP and jQuery are needed. If you want to try there is a working demo. All source code for the demo can be found from GitHub.

PHP for Receiving the Files

PHP has easy way of accessing uploaded files using $_FILES array. For demo’s sake I am assuming we should upload only image files. Nothing special here. Just you plain old upload script.

<?php

$error_message[0] = "Unknown problem with upload.";
$error_message[1] = "Uploaded file too large (load_max_filesize).";
$error_message[2] = "Uploaded file too large (MAX_FILE_SIZE).";
$error_message[3] = "File was only partially uploaded.";
$error_message[4] = "Choose a file to upload.";

$upload_dir  = './tmp/';
$num_files = count($_FILES['user_file']['name']);

for ($i=0; $i < $num_files; $i++) {
    $upload_file = $upload_dir . basename($_FILES['user_file']['name'][$i]);

    if (!preg_match("/(gif|jpg|jpeg|png)$/",$_FILES['user_file']['name'][$i])) {
        print "I asked for an image...";
    } else {
        if (is_uploaded_file($_FILES['user_file']['tmp_name'][$i])) {
            if (move_uploaded_file($_FILES['user_file']['tmp_name'][$i], 
                $upload_file)) {
                /* Success... */
            } else {
                print $error_message[$_FILES['user_file']['error']];
            }     
        } else {
            print $error_message[$_FILES['user_file']['error']];
        }    
    }    
}
?>

Initialize Gears and Setup Drop Target

Gears will be initialized after DOM is ready. We need desktop support for dragging and dropping. HTTPrequest support is used for uploading the files.

We will also setup event handlers for file dropping. Target is a div with id #dropzone. Different browsers use different event names. Unfortunately jQuery does not normalize native dropping event name. Thus we need to initialize the event separately for each browser. We also need to use native DOM element fetched with .get(0). When drop happens upload() function will be called.

When dropping a file default action for browser would be top open it. With Safari and IE this can be prevented by false from dragover event. With FireFox you need to call event.stopPropagation() later in the upload() code.

$(function() {
    
    /* Google Gears support. */
    var desktop = google.gears.factory.create('beta.desktop');
    var request = google.gears.factory.create('beta.httprequest');
    
    /* We cannot use $.bind() since jQuery does not normalize the */
    /* native events. */
    if ($.browser.mozilla) {
        $('#dropzone')
            .get(0)
            .addEventListener('dragdrop', upload, false);
    } else if ($.browser.msie) {
        $('#dropzone')
            .get(0)
            .attachEvent('ondrop', upload, false);
        $('#dropzone')
            .get(0)
            .attachEvent('ondragover', function(event) { 
                event.returnValue = false; 
            }, false);                
    } else if ($.browser.safari) {
        $('#dropzone')
            .get(0)
            .addEventListener('drop', upload, false);        
        $('#dropzone')
            .get(0)
            .addEventListener('dragover', function(event) { 
                event.returnValue = false; 
        }, false);
    }
};

Upload Files With JavaScript

There are two ways of uploading files with JavaScript. I will emulate normal HTML multipart/form-data type of form. PHP has native support for uploading files this way. JavaScript side will be slightly more complicated though.

Gear blobbuilder is used for building a string according to RFC2388. Most of the code in upload() function is about building the multipart/form-data string. This string is then submitted to PHP script with Gears httprequest.

If upload.php returns something we assume it is an error and we alert() it. Otherwise we update the content of #dropzone with a thumbnailed list of images. This list is generated by list.php script.

function upload(event) {

    var data = desktop.getDragData(event, 'application/x-gears-files');
            
    var boundary = '------multipartformboundary' + (new Date).getTime();
    var dashdash = '--';
    var crlf     = '\r\n';
    
    /* Build RFC2388 string. */
    var builder = google.gears.factory.create('beta.blobbuilder');

    builder.append(dashdash);
    builder.append(boundary);
    builder.append(crlf);
    
    for (var i in data.files) {

        var file = data.files[i];
        
        /* Generate headers. */
        builder.append('Content-Disposition: form-data; name="user_file[]"');
        if (file.name) {
            builder.append('; filename="' + file.name + '"');
        }
        builder.append(crlf);
        
        builder.append('Content-Type: application/octet-stream');
        builder.append(crlf);
        builder.append(crlf); 
        
        /* Append binary data. */
        builder.append(file.blob);
        builder.append(crlf);

        /* Write boundary. */
        builder.append(dashdash);
        builder.append(boundary);
        builder.append(crlf); 
    }
    
    /* Mark end of the request. */
    builder.append(dashdash);
    builder.append(boundary);
    builder.append(dashdash);
    builder.append(crlf);        
        
    request.onreadystatechange = function() {
        switch(request.readyState) {
            case 4:
                /* If we got an error display it. */
                if (request.responseText) {
                    alert(request.responseText);
                }
                $("#dropzone").load("list.php?random=" +  (new Date).getTime());
                break;
        }
    };
    
    /* Use Gears to submit the data. */
    request.open("POST", "upload.php");
    request.setRequestHeader('content-type', 
        'multipart/form-data; boundary=' + boundary);
    request.send(builder.getAsBlob());
    
    /* Prevent FireFox opening the dragged file. */
    if ($.browser.mozilla) {
        event.stopPropagation();
    }
    
}

Other Demo Code

For demo’s sake there is a simple script which produces thumbnails of uploaded images. It uses ImageResize class taken from Peter Gassners Image Resize Frog CMS plugin.

<?php

require_once 'ImageResize.php';

foreach (glob("./tmp/*") as $source) {
    $file = pathinfo($source);
    if (!preg_match('#-100x100\.([a-z]+)$#i', $source)) {
        $destination = $file['dirname'] . '/' . $file['filename'] . 
                               '-100x100.' . $file['extension'];
        ImageResize::image_scale_cropped($source, $destination, 100, 100);
        printf('<a href="%s"><img src="%s" widht="100" height="100" /></a>', 
                 $source, $destination);
    }
}
?>

Known Issues

Gears is currently broken with FireFox 3.5.x. Hopefully issue will be fixed with next release. If you find any other issues or have feedback feel free to leave comment.



When asking a question please include an URL to example page where the problem occurs. If you have longer code examples please use pastie.org.
CATEGORIES
Built using the awesome Flat UI Pro framework by Designmodo.

© 2013 Mika Tuupola.