Howto: Build an HTML5 file uploader with Ajax

As an old friend of Flash and the possibilities we got to improve the web experience years ago I am very curios about what is possible without Flash nowadays.

So the first thing I wanted to know, is how to develop a file uploader based on web standards with having the ability to show a progress bar. Now that I write this post, this is a very hot topic because I neither found much information about it nor did I see any website having this feature except Youtube.

As we are talking about the client side of the uploader first it’s all about javascript now. So, a basic understanding of this language is necessary.

Still with me? Alright, what do we need to build this uploader? Simple said it’s two features named File API and XHR2. Never heard of? No problem. Let’s first have a look at the File API.

The File API

The API provides access to the files stored on your hard disk and numerous file operations on these. Having a closer look you recognize that the API includes a bunch of new objects with powerful functionality bringing the web and your desktop very close together. But to build an uploader we just focus on two objects:

  1. FileList
  2. File

While the File object represent a real file on your hard disk and lets you access its information through some properties and methods the FileList object acts as an array or a storage of File objects you can easily iterate through.

To select a file we use the following input tag, introduced 1997, when uploading 100k must have been a pain in the ass for everybody.

<input type="file" id="file" />

Clicking on the button a window appears that lets you select a file. Sure, you are used to expect nothing as you chose one but you can change this by binding an event handler to the input field.

var input = getElementById('file'); 
input.addEventHandler('change', callbackFunction);

function callbackFunction(event) {
    var FileList = event.target.files;
    var File = FileList[0];
}

In line 5 we find all the magic. We can access a FileList object through the Event object that is fired when changing the input element. We always get a FileList, even when only selecting one single file but in line 6 you can see how to access it. I recommend you to build this small example, install Firebug and debug the File var.

That’s it for the File API part. Next we have a look at XHR2.

XMLHttpRequest2

I’m sure you heard of XMLHttpRequest, the magic that brought Web 2.0 to life. The XHR object gave us the possibility of asynchronious HttpRequests so we can load/send data from/to a server without doing a page reload. So we were able to send/upload data ever since this object was available first but with the support of the second version we got a new property named upload. Here some code for a better understanding:

xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", onUploadProgress);

// void open(DOMString method, DOMString url, boolean async);
xhr.open('post', 'url', true);

xhr.upload is of type XMLHttpRequestUpload, a new object that is shipped with a special Event called progress that is fired as long as the upload of data takes time giving information about the current upload status.

xhr = new XMLHttpRequest();
xhr.upload.addEventListener("progress", onUploadProgress);

// void open(DOMString method, DOMString url, boolean async);
xhr.open('post', 'url', true);
xhr.setRequestHeader("Content-Type", "application/octet-stream");
xhr.setRequestHeader("X-File-Name", File.name || File.fileName);
xhr.setRequestHeader("X-File-Size", File.fileSize);
xhr.setRequestHeader("X-File-Type", File.type);

if ('getAsBinary' in File)
	xhr.sendAsBinary(File.getAsBinary());
else
	xhr.send(File);

function onUploadProgress(event) {
    if(event.lengthComuptable) {
        log(Math.round((event.loaded * 100) / event.total) + '%');
    }
}

Here we got a bunch of new lines. First of all we have to imagine that we have a variable called File in this context that holds a real File object we chose using the input field. As you can see we set some http headers to let the server know how to handle the uploaded file. In line 7 we access the filename either through the name or fileName property of our File object wheras name is the standard and fileName is how Firefox 3.5 named this property.

Next we start the upload with xhr.send(File). Due to Firefox 3.5 there is an exception in Line 12 again.

After starting the upload the callback function onUploadProgress is fired as long as the upload isn’t finished. As you can see we can access the bytes already loaded and the total bytes to compute the percentage of our upload progress. That’s very equal to working with ActionScript.

Last and least there are two possibilities to to handle the upload success as the xhr object itselt and the xhr.upload property may fire an Event on success. But while the xhr object may retriece data from the server, e.g. some JSON, the upload property just informs you about whether the upload has failed or not.

The Server Side (PHP)

Depending on the server technology you are using the processing of uploaded data is different. In this case we have a look at how to handle the upload with PHP as this is widely used.

The traditional way of uploading and processing  files is through an HTML form that sends the data via POST to the server where the uploaded file is available in the global $_FILES var. Using xhr there is no $_FILES var but you can directly access the PHP input stream.

$fileName = $_SERVER['HTTP_X_FILE_NAME'];

$input = fopen('php://input', 'r');
$output = '/your/path/' . $fileName;

while ($data = fread($input, 1024))
	fwrite($output, $data);

fclose($input);
fclose($output);

There is just one more thing I want to show you. Imagine the user wants to upload a huge file, let’s say 50M. You might want to give him the chance to cancel the upload to not wait until the upload is finished. But with this script a file will be written even if  you uploaded just 1K. This can cause a lot of trash on your server and I hate waisted files. Hope, you too.

The solution is quite easy as we just have to compare the filesize of the uploaded file with the filesize we set in the http header.

$fileName = $_SERVER['HTTP_X_FILE_NAME'];

$input = fopen('php://input', 'r');
$output = '/your/path/' . $fileName;

while ($data = fread($input, 1024))
	fwrite($output, $data);

fclose($input);
fclose($output);

if(filesize($fileName) != $_SERVER['HTTP_X_FILE_SIZE'])
	unlink($fileName);

If the sizes don’t match, the written file will be deleted and no trash is left behind.

Useful Links:

 

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>