AJAX Uploading with HTML5′s File API

Posted by in Code

Prior to HTML5 and its File API, getting media from a user was a somewhat sordid affair. You generally either had a static page with an <input> element of a <form> posting to a processing file or utilized some third party script that relied on a Flash bridge or <iframe> shenanigans to asynchronously get the file where it needed to go.

This leads to things like this demo and how you can drag ‘n drop (‘n upload) files with Gmail. Seeing as how I was tired of doing the aforementioned Flash or <iframe> bullhonky, I decided it was time to get the ball rolling for myself.

Accessing the Files

It’s actually surprisingly simple to see what a user has chosen to upload in a <input> (of a file type, of course). You can access all of the vital pieces of information through JavaScript once they’ve been selected, and I say “they” because if you specify the multiple attribute in the <input>, you can then select multiple items:

1
2
3
4
5
6
7
8
9
document.getElementById('file-input-element').onchange = function(){
    if(this.files.length < 1) return false;
    for(var i = 0; i < this.files.length; i++) console.log(this.files[i]);
};
/**
 * name - file name
 * size - file size in bytes
 * type - file MIME type
**/

Aside from the attributes, there are also some pretty sweet functions like getAsDataURL() and getAsText() which can have some cool uses (such as dynamically loading an image as a CSS background image in data form), but we’ll have to save that for another day. All we need right now is access to that files array.

Uploading the Files

It starts pretty much the same as any other AJAX data transfer, which is with a XMLHttpRequest object. The only difference is that since usually everything I do is with a GET request, I just put any key-value pairs in the URL and access the information server side. However, since the file data may (and probably will) exceed the URI size limit, it’s best to go with POST.

So then you may be asking “how do we get the files to go along with the XHR request?” It’s actually quite simple: just use a FormData object.

1
2
3
4
5
6
7
8
9
10
11
12
var xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress',function(ev){
    console.log((ev.loaded/ev.total)+'%');
}, false);
xhr.onreadystatechange = function(ev){
    // Blah blah blah, you know how to make AJAX requests
};
xhr.open('POST', url, true);
var files = document.getElementById('file-input-element').files;
var data = new FormData();
for(var i = 0; i < files.length; i++) data.append('file'+i, files[i]);
xhr.send(data);

You might be wondering why not just use the FileReader interface and upload with something pre-built like the jQuery $.ajax() method, and you’ll find the answer in the progress event listener; the $.ajax() method and the like doesn’t allow for it and I really want to display an upload progress bar, dammit!

Is that it?

Yeah, that’s it. Pretty easy, right? On the server side, the files are accessed just as if they were uploaded via a form and POSTed (in PHP’s case, they’re located all in the $_FILES array). I bundled it all up in a nice little JavaScript object which you can find over at my GitHub complete with options and some helpful methods. Have fun with it and be sure to leave a comment if you think of something I could/should change.

View the demo (requires an HTML5-capable browser). I hacked together my own version of a <progress> element since at the time of this writing, only WebKit browsers have it sufficiently implemented (though I can’t seem to figure out why it doesn’t fully function as expected in Opera).