AJAX file uploads, how do they work?! Well, they kinda don't.
Browsers don't allow file uploads via XMLHttpRequest (aka XHR) for security reasons. If we try to submit a form remotely via XHR, it will work, except with the file field stripped out of the request parameters. This sort of partial, silent failure can lead to unicorn black-eyes. (I punch unicorns in the face when I'm frustrated.)
This is how jQuery's standard AJAX functions work, including
In turn, the Rails 3 jQuery UJS driver uses jQuery's standard AJAX functions internally. To prevent this sort of silent failure in Rails 3, we added the
ajax:aborted:file event to abort the remote form submission if any non-blank file inputs are detected.
So, there are a couple workarounds that give the impression of AJAX file uploads (and thus the same workflow and UI from the user's perspective), without actually submitting the file via XHR.
One method is to use Flash. Since Flash makes connects to the server outside the scope of the browser's connection, it essentially plays by it's own rules.
Another, more popular method (since it works in browsers without requiring Flash to be installed) is known as the iFrame method. And here's how it works.
From here on, I'll be referring specifically to the
ajaxSubmit() method built into the jQuery form.js plugin, but the concepts are generally the same for any library that uses the iFrame method.
Assuming we have a form with a file-type input field, the iFrame method of uploading files can be summarized in the following steps:
submitevent to execute our custom iFrame-method function (e.g.
.ajaxSubmit()from the form.js plugin).
targetattribute of the form, such that the results of the form submission are rendered in the new iFrame instead of the current window.
targetattribute to its original value, and inform the
.ajaxSubmit()(from form.js) function's callback hooks that the submission is complete.
It's that easy! Er, not quite...
The above steps have a couple implications to further complicate things...
Since the submission takes place normal-style in an iframe, the parent window cannot inspect the response headers or status code (from step 5 above), due to browser security restrictions.
.ajaxSubmit() in the parent window must assume, once the request is completed, that it was successful. There are ways around this, but as of this writing, they have not yet been pulled into the form.js plugin.
Furthermore, if a JS response was returned, it won't get executed in the parent window, because it was returned to the iframe. We would thus need to manually check if the response was JS code (instead of HTML markup or some other format), and execute it ourselves in step 7 above.
Now that I've set the stage, in my next article, I'll show how we can seamlessly add AJAX file upload capabilities to Rails 3 by extending jquery-ujs.
Thanks to Gábor in the comments below for bringing this up and prompting me to add this section.
Of course, we must ask, why are the browsers making it so hard for us to do something so simple? After all, is it really making the user's interaction any more secure if there is such a workaround, as described here, which is as useful as it is popular? The answer is, no not really. And that is why the newest browsers have started to implement an actual AJAX file upload API.
As mentioned in the comments, we can see that a few of the latest browsers are already supporting this. For an idea of how to use this API, check out Mozilla's documentation for using it with Firefox 4.