HTML 5 Drag and Drop and Automatically Send to the Server

Now Reading
HTML 5 Drag and Drop and Automatically Send to the Server

I realised (when looking myself) that there are a lot of demos and tutorials that show you how to drag-and-drop a file into the browser and then render it on the page. They’re often labelled as “drag-and-drop and upload”, but they actually don’t upload. This tutorial will take you that final step.

I’ll walk you through an example that you can play with: drag a file into the web page, and it’ll be uploaded to the server instantly.

 

Tasks #

Let’s break this process down into specific tasks:

  1. Capture the drop event and read its data.
  2. Post that binary data to the server.
  3. Provide feedback on the progress of the upload.
  4. Optionally render a preview of what’s being uploaded and its status.

To achieve all of this, we need the following HTML5 and non-HTML5 APIs:

  1. Drag and Drop
  2. FormData
  3. XHR progress event
  4. FileReader

Keep in mind that not all browsers support all of this technology today, but they’re getting close. I just wanted to create a clear and complete tutorial on how to go the whole hog and upload that dropped file.

The end result: we’re able to drop the file anywhere in the browser, we get a preview and progress of the upload, and it’s a slick experience.

dnd-upload
Our drag-and-drop example page

Drag and Drop #

Just as a forewarning, drag-and-drop has been known to be a bit of a killjoy. In fact, it’s a nightmare, but I won’t repeat what’s been saidbefore.

Our example is going to allow you to drag-and-drop a file anywhere within the browser. To achieve that, we need to attach our event listeners to the body or documentElement (i.e., the root HTML node). By listening on the documentElement, this code could be anywhere in the page, as documentElement will always exist. You can only listen on body once the <body> element has been encountered.

Here’s the pattern of code we need to capture the drop event on the entire document:

var doc = document.documentElement; doc.ondragover = function () { this.className = 'hover';return false; }; doc.ondragend = function () { this.className = ''; returnfalse; }; doc.ondrop = function (event) {   event.preventDefault && event.preventDefault();   this.className = '';   // now do something with:   var files = event.dataTransfer.files;   return false; };
Basic pattern for capturing drop events

I’m using the hover class so that I can toggle a tip explaining to the user that the file can be dropped on the page.

Inside the drop event, we can access the dropped files viaevent.dataTransfer.files.

An Alternative to Drag and Drop #

Annoyingly, as I write this, I realise this combination of API requirements is currently only met by Chrome and Firefox. So, taking the test from Modernizr, we can test for support and provide our alternative:

var dndSupported = function () {   var div = document.createElement('div');   return ('draggable' in div) || ('ondragstart' in div &&'ondrop' in div); }; if (!dndSupported()) {   // take alternative route }
Testing for drag-and-drop support

Instead of drag-and-drop, we can insert a file input element (I’ve given it an id of “upload”), and when its value is changed, the file can be scooped in:

document.getElementById('upload').onchange = function (event){   // `this` refers to the element the event fired upon   var files = this.files; };
An alternative to native drag-and-drop

You can see the equivalent is simply the files property of theHTMLInputElement object. This will give us access to the same file as the event.dataTransfer.files from the drop event.

Automatically Uploading the File #

This is the neat bit, and it’s painfully simple. We use a feature of the XHR2 spec: FormData. Once we create an instance of FormData, we can append items to it:

var formData = new FormData(); for (var i = 0; i < files.length; i++) {   formData.append('file', files[i]); } // now post a new XHR request var xhr = new XMLHttpRequest(); xhr.open('POST', '/upload'); xhr.onload = function () {   if (xhr.status === 200) {     console.log('all done: ' + xhr.status);   } else {     console.log('Something went terribly wrong...');   } }; xhr.send(formData);
Creating and posting FormData

Yeah, that’s it.†

Let’s look at a couple of key things that are going on the above code.

formData.append('file', files[i]);

We’re sending named parameters to our server, specifically an array of values called file. Obviously, you can call it what you want, but the file is the name your server will be looking for when it saves the uploaded file (or files).

xhr.onload = function () {   if (xhr.status === 200) {     console.log('all done: ' + xhr.status);   } else {     console.log('Something went terribly wrong...');   } };

If you’re familiar with XHR, you’ll notice we aren’t listening toonreadystatechange, only onload — which is (in effect) a convenience function to let you know when the readyState is 4 (i.e., loaded!). You should still check and respond appropriately to the status code of the request to ensure it’s 200 OK, rather than a 500 (internal server error) or 404 (not found) or anything else.

xhr.send(formData);

The nice trick here is that XHR has automatically set the encoding of the posted data to multipart/form-data. This encoding allows your server to read and save the files. It’s like the encoding used when you send an attachment in an email.

Providing Feedback to the User #

XHR2 now (flippin’ finally) comes with a progress event. So if you’re sending or retrieving a large file via XHR, you can tell the user how far along you are.

It’s pretty simple. If you want to track the progress of the XHR request, listen to the progress event. One gotcha that caught me out for some time: I needed to track the progress of the upload, not the download. To do this properly, you need to listen for progress on theXHR upload object, as so:

xhr.upload.onprogress = function (event) {   if (event.lengthComputable) {     var complete = (event.loaded / event.total * 100 | 0);     progress.value = progress.innerHTML = complete;   } }; xhr.onload = function () {   // just in case we get stuck around 99%   progress.value = progress.innerHTML = 100; };
Providing feedback with XHR2’s progress event

Note that instead of xhr.onprogress, we usexhr.upload.onprogess. When this event fires, we check that the event supports calculating the amount of data uploaded (thelengthComputable part), and then calculate the amount completed.

In my HTML, I’m using a <progress> element (similar to the<meter> element, but more semantically appropriate as we’re presenting the progress of a task) to show the user how much of their file has been uploaded:

<progress id="progress" min="0" max="100"value="0">0</progress>

Note that I’m using innerHTML for browsers that don’t yet support<progress>. Then with a dash of CSS-generated content, both theinnerHTML and value of the progress can be the same (perhaps an over optimisation, but I was rather proud of myself at the time!).

And Finally, Rendering a Preview #

As a nice addition, we’ll give the user a preview of what we’re uploading for them. It requires the File API — specifically theFileReader API.

As the drag-and-drop operation (or the input[type=file]) contains a file object, we can hand this over to the FileReader to get the contents of the file. If it’s something like an image, we can get the Base64 data and give the user a preview. Or if it’s text, we could render it in a <div>. The MIME type for the file is available as thetype property on the file object.

So let’s assume we only accept images. Here’s the preview part that runs after the file has been received by the browser:

var acceptedTypes = {   'image/png': true,   'image/jpeg': true,   'image/gif': true }; if (acceptedTypes[file.type] === true) {   var reader = new FileReader();   reader.onload = function (event) {     var image = new Image();     image.src = event.target.result;     image.width = 100; // a fake resize     document.body.appendChild(image);   };   reader.readAsDataURL(file); }
Rendering a preview of an uploaded image

We create the FileReader and give it a file to read. In the above example, I’ve used readAsDataURL, but you can also read files in plain text and binary formats (as per the spec).

When the reader has read the file and the data is available, it fires theload event, which in turn creates our new image.

The result of the file read action is in the event.target.resultproperty. In the case of the readAsDataURL, the value is along the lines of data:image/png;base64,ABC....

Using All the Tools in the Shed #

This example may be a little contrived for the article that I wanted to write. I’ve used many different technologies to do something that’s a fairly common pattern on the web. In fact, it was because I had trouble finding a definitive source on uploading to the actual server (except, of course, as I wrote this article I found this example from back in late 2010!).

Hopefully, you’ll see how each bit of tech can work together to create an application, but equally I hope that you can see where this would work if none or just some of the technology wasn’t available. Make sure you detect functionality. Make sure you provide fallbacks. Make sure you develop responsibly.

Here’s the final drag-and-drop and upload demo for you to play with.

Read Full Article

  • Kristian Kaelin3

    Useful discussion . Incidentally , if someone requires to merge PDF or PNG files , my colleague came across post here http://goo.gl/zwKMGl