Back on April 15th, 2010 Google announced that you could drag and drop attachments in their mail client – Gmail. (http://gmailblog.blogspot.com/2010/04/drag-and-drop-attachments-onto-messages.html). I thought that was a pretty neat idea and went searching for answers. After some research I managed to create my own Drag and Drop File Uploading system with HTML 5 and the latest W3C standards.

Please note not all browsers are HTML 5 capable. Based on my research Safari 5 (on the mac), Chrome 6 and Firefox 3.6+ have the right methods and are HTML 5 capable of the Drag and Drop Upload

For this to work, I plan on using the <canvas> tag – I have a previous post explaining the canvas tag here (http://www.josh-ho.com/blog/?p=314).

<canvas id="myCanvas">Your browser does not support the canvas tag

According to the W3C, I can apply a dragenter, dragleave, dragover and a drop event on objects with Javascript. In that case I applied an event listener to my canvas for those events.

var canvas = document.getElementById("myCanvas");
canvas.addEventListener("drop", drop, false );
canvas.addEventListener("dragenter", dragEnter, false);
canvas.addEventListener("dragleave", dragExit, false);
canvas.addEventListener("dragover", dragOver, false);

function dragEnter(evt) {
evt.stopPropagation();
evt.preventDefault();
}

function dragExit(evt) {
evt.stopPropagation();
evt.preventDefault();
try{
canvas.style.opacity = 1;
canvas.style.MozOpacity = 1;
canvas.filters.alpha = 100;
} catch(err){}
}

function dragOver(evt) {
evt.stopPropagation();
evt.preventDefault();
try{
canvas.style.opacity = 0.5;
canvas.style.MozOpacity = 0.5;
canvas.filters.alpha = 50;
} catch(err){}
}

function drop(evt) {
evt.stopPropagation();
evt.preventDefault()
}

There are 4 event listener functions that I created for each of the Event Listeners. When I drag anything I want to make sure that no other events are being fired so I have to call evt.stopPropagation and evt.preventDefault. What these two event methods do is stop other events from bubbling to the top and causing a misfired or unintended fire event. I also wanted to make my <canvas> element to replicate a similar functionality where the canvas opacity / alpha dims down to 50% when you drag your file over, and return back to normal when your mouse leaves the canvas.

The browser knows if you are dragging an object inside of the browser and if you are dragging a File into the browser. Which is what we want to do. To grab the file we can use the dataTransfer Interface to return the object to us. In our case it would return the file that we dragged into the browser.

var file = evt.dataTransfer.files[0];

(The code above only gets the first file the user dragged. If the user is dragging multiple files, you can run a loop to loop through the file index).

We now have to turn our attention to the drop function. Here is where we are using the new methods outlined by the W3C to upload a file. I have found that Firefox handles file uploading differently than Chrome and Safari. In order for this to work on the different browsers, I ended up writing to different versions of the File Upload in javascript and used a browser detect to determine which code to use. However as more and more browsers become HTML 5 ready, and browers start to implement the W3C standards, this may end up being unnecessary and one way to write the code would suffice.

Each of the browsers needed to call an XMLHTTPRequest and provide a URL to the request to submit the data to. This is no different than calling the <form> object and passing in the URL for the action property. I created a variable called file which would contain the first file that you dropped on my canvas.

var url = "upload.php";
var request = new XMLHttpRequest();
var file = evt.dataTransfer.files[0];
request.open( "POST", url, true ); // open asynchronous post request

request.onreadystatechange=function(evt) {
if (request.readyState==4 && request.status==200) {
playlist( request.responseText, file.fileName);
}
}

... more code to follow

function playlist(location, name) {
var newobj = document.createElement('p');
newobj.innerHTML = '<a href="' + location + '">' + name + "</a>";
playListElement.appendChild(newobj);
}

When the XMLHTTPRequest is complete, I intend on allowing the user to click on a link so that they could download the file that they have uploaded.

Now earlier I stated that Firefox is different than Chrome and Safari in terms of uploading files.

For Firefox I had to create the multiformboundary, which is the information the <form> uses when it passes data through the POST or GET method. In the multiformboundary I attached the file and the file name so in the backend, I can grab the file. Once I created the file I can call the sendAsBinary method, pass in my newly created multiformboundary and send away. This method is only limited to Firefox and cannot be used by other browsers.

var boundary = '------multipartformboundary' + (new Date).getTime();
var dashdash = '--';
var crlf = '\r\n';
var builder = '';

builder += dashdash;
builder += boundary;
builder += crlf;
builder += 'Content-Disposition: form-data; name="user_file"';
if (file.fileName) {
builder += '; filename="' + file.fileName + '"';
}
builder += crlf;
builder += 'Content-Type: application/octet-stream';
builder += crlf;
builder += crlf;

/* Append binary data. */
builder += file.getAsBinary();
builder += crlf;

/* Write boundary. */
builder += dashdash;
builder += boundary;
builder += crlf;

/* Mark end of the request. */
builder += dashdash;
builder += boundary;
builder += dashdash;
builder += crlf;

/* FOR FIREFOX 3.6 ONLY */
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
request.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary); // make sure to set a boundary
request.setRequestHeader("Connection", "close");
request.sendAsBinary(builder);

For both Chrome and Safari I was able to use a new method called FormData. What FormData does is that it handles all of the multiformboundary and all I need to do is attach my variable name and my object. Now there is a big difference in what the new XMLHTTPRequest and the old XMLHTTPRequest can do. Prior to the W3C standards update, the XMLHTTPRequest could only take in strings. Now after the standards update, the XMLHTTPRequest can accept blob = binary large object. As a result we are now able to pass in our FormData through the XMLHttpRequest.

var formData = new FormData();
formData.append("user_file", file);
request.send(formData);

Now we are pretty much done. All of the javascript and HTML code has been written out. All we need to do is write a PHP file upload script that would take the file we sent it and actually upload the file. This would be our “upload.php” as defined in our variable ‘url’. What this code will do is print out the path of where the file is so when the XMLHTTPRequest is completed I can use the path of the file as a way to link the user to download their file that they have just uploaded.

$target_path = "upload/".$_FILES['user_file']['name'];
if(move_uploaded_file($_FILES['user_file']['tmp_name'], $target_path)) {
echo $target_path;
}
?>

And there we have it. An HTML 5 Drag and Drop Upload that works.
I have attached my own file that you can download if you want – Drag and Drop Upload


This post is tagged , , , , , , , ,

10 Responses

  1. Scott says:

    Hmm.. doesnt seem to work. I downloaded and extracted and ran it but nothing happens when I drag files in.

  2. Josh says:

    I realize that I am missing a css file in the zip file. I have updated the zip file.

    Try downloading the zip file now.

  3. Dec says:

    Not working for me, getting:

    Failed to load resource: the server responded with a status of 500 (Server Error) – Upload.php

  4. Josh says:

    Hello Dec

    There are a couple of small requirements that I’ll put on the list.
    – Can you check that your php is setup properly and is running on your server. I would recommend PHP 5, but the code runs on PHP 4.
    – Do you have the upload folder’s file permissions set to read/write ( CHMOD – 777 )?

    If worse comes to worse, try downloading my example and uploading the folder on your server. Once you have uploaded everything, change the upload folder to read write ( CHMOD – 777 ) and try that out.
    Link: http://www.josh-ho.com/blog/Files/draganddropupload.zip and upload

  5. santa pola says:

    Thank you very mucha fantastic blog like this. been my salvation for my page

  6. Thank you very muchimpressive blog long time looking for. my guide for my page

  7. I utterly liked reading about HTML 5 Drag and Drop File Upload – Joshua Ho's Blog and thought it was well worth the read. The only other site I found on Google wasnt as good as this one, thanks.

  8. named says:

    Doesnt seem to work with Firefox 3.6.13

  9. Josh says:

    I can confirm that this does work in 3.6.13.

    You may need to set the file attribute on the folder where your files are being uploaded to 777 – Read and Write access. If you are using my example, you can right click on the “upload” folder and apply a CHMOD to 777 or allow the folder to have read and write capabilities.

  10. p90x sale says:

    Its like you learn my mind! You seem to grasp so much approximately this, such as you wrote the e-book in it or something. I believe that you can do with a few % to pressure the message home a bit, however other than that, that is great blog. An excellent read. I’ll certainly be back.

Leave a Reply

Categories