Friday, January 11, 2013

Asynchronous File Upload

So this post will be a little different. Rather than the usual career/life advice, this one is pure geek craic for UI Engineers.

Before we begin, I will note that I have not actually tested the code provided here, so if you're wanting to use it in a production environment, be sure to tweak it to suit your needs and test, test, test. Also, if you're reading this, I'm going to assume that you know what Asynchronous File Upload is - I seriously doubt you would be reading it otherwise.

When we talk about an asynchronous file upload, we'd like to be talking about a pure XmlHttpRequest solution; unfortunately, that doesn't really work. None of the browsers allow it directly, and IE is more picky than the others - so here's how we're going to do it.

First, make sure your form is marked up correctly. If you're not using progressive enhancement, shame on you. Even though about 99% of users have JavaScript enabled on their user agents, not everyone does, and it should just be considered part of the job to make sure your app works for everyone. So, check the encoding of the form to make sure that it will allow file uploads.

Now that you have your form created (and working without JavaScript), let's start to layer the JavaScript on top. As I mentioned earlier, there are basically two groups IE and not IE. In our case, we're not going to sniff which version(s) a person is using, we're going to look for support of the FormData object to determine how to handle the asynchronous request. That means the bulk of our code is going to be wrapped in an if...else using window.FormData as the conditional. Since we're dealing with a form, let's create a simple function that takes the form as an argument and does our check...which gives us something like...

function foo(frm) {
  if (form) {
    if (window.FormData) {
      /* use an XmlHttpRequest object */
    } else {
      /* we have to use an iframe */
    }
  }
}

From there it's pretty simple. If you have access to the FormData object, create a new object and append the file to it using the 0 element of the files property of the file object. You can do this by using something like...

var file_input = document.getElementById("my_file"),
    frm_data = new FormData();
frm_data.append(file_input.files[0]) ;

Then pass the FormData object in the send method of the XmlHttpRequest and watch for the request to complete as you normally would.

Of course, if you don't have access to the FormData object, you'll need another way to do an asynchronous upload. Here's where it gets a little tricky, because you have to manage the target attribute of the form you're submitting. If the file is the only input in the form, then it's a simple matter; if it's not and you want the form to actually submit and the response to be displayed in the user-agent when not uploading a file, you'll have to build in a mechanism to reset the target attribute. Otherwise, this is pretty simple as well - just create or retrieve an IFRAME (make sure it's hidden from everything, even accessibility tools by setting the display to "none") and use that object's ID as the value in the TARGET attribute of the form. When the response comes in, it will go into the IFRAME.

By using the onload event of the IFRAME and monitoring the response of the XmlHttpRequest, you can even update the document when the upload is complete.

That's it. I know there are a few details I haven't discussed here, but a library (called FileUploader) will be posted to the repository on cathmhaol.com in the coming days.

No comments:

Post a Comment