AJAX File Uploads with the iFrame Method

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 .ajax().
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.
Workarounds
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.
The iFrame method
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:
- Hijack the forms
submitevent to execute our custom iFrame-method function (e.g..ajaxSubmit()from the form.js plugin).
- Using JavaScript, create an iFrame element and insert it into the current page (and make the iFrame tiny and invisible to the user).
-
Thanks to spinn over on Reddit for catching this. I’ve updated this step to illustrate the more correct way.Thanks to malsup in the comments for keeping me honest here and suggesting a further clarification.
Change the
targetattribute of the form, such that the results of the form submission are rendered in the new iFrame instead of the current window.
- Submit the form to the iFrame normal-style (non-AJAX).
- Allow the iFrame to navigate to the response page (since it was submitted normal-style). Note that because this happens in the iframe, the parent window does not go to a new page or get redirected, only the hidden iFrame.
- Copy the response content from the iFrame back into the parent window.
- Delete the iFrame, reset the form’s
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…
Complications
The above steps have a couple implications to further complicate things…
Response headers and status code
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.
So, .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.
Response JavaScript not automatically executed
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.
Form.js has a workaround which allows you can return some javascript code inside a textarea element as the response content. So then, if the iframe response content contains a textarea with some text value once the iframe form is submitted, it will copy that textarea content back to the parent window and execute it as javascript.
AJAX file uploads in Rails 3
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.
Future-proof
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.

you can upload files using ajax, you just need a recent browser (http://caniuse.com/#feat=fileapi) . see for example https://developer.mozilla.org/En/Using_XMLHttpRequest#Sending_files_using_a_FormData_object
Hi Gábor, that’s a very good point. I suppose to future-proof this article, I should mention that. Thanks for the resources, I’ve add those to the article as well.
Nice article, Steve. Just a couple of nits…
1. Nothing is every copied *to* the iframe.
2. The form is not submitted to the iframe. Forms are always submitted to the server. The iframe merely receives the response.
Hey Mike, thanks for the feedback! I love the form.js plugin, btw, so thanks for the great work there.
Yes, absolutely, this was a misconception on my part back when I first started writing the article. Luckily, the reddit crew set me straight on that one, and I’ve since updated the article.
That is correct. I meant to illustrate conceptually that the form is being submitted in the context of the new iframe rather than the current window, meaning the server receives the request from the iframe, instead of the current window. By the W3C definition:
So, more specifically, it’s not just that the iframe receives the response, the iframe actually sends the request too.
I’ve updated the article to be more clear on this point. Thanks again, Mike!
In the case of the jQuery Form plugin, the iframe does not send the request. The iframe is the target of the response, not the request. The request is sent from the main window via native browser submit.
Ah yes, of course form.js sets the form’s
targetattribute just like anything else. I had always gone by the W3schools definition of thetargetattribute:Which is ambiguous and seems to imply that the action URL is opened from the target. However, with a little more searching, I was able to find this clearer definition, which states:
I submitted a suggestion for the W3schools site to maybe restate the definition of target as:
Thanks for keeping me honest!
Nice acticle. We’ve been using this method to allow for image “previews” when uploading images using a form, with some slight variations.
The way we approached it, was that the file prompt the user sees is actually in the iframe rather than the page, and the iframe form is submitted on the change event for the file element. The form posts the file and performs an action (in our case saves the image to a directory.) Then a Javascript function on the parent is called to report the file upload success/failure (we use this function to generate the preview image from the image written to the server and hide the iframe until the user clicks a link to change the image.)
You can see it in practice on http://www.whatwasthere.com (coded by some fellow workmates of mine at Enlighten.) It is the Upload Photo functionality (an account is needed.) I’m currently using similar code in 2 other projects, but neither of them are live yet.
Awesome, I really liked your idea. It is simple and very effective.
i have tried this approach, but have hit a wall. seems i cant seem to get it to be cross browser. fixing it in one browser breaks in the next. with IE9 jquery cannot find the form element in the DOM so a ‘submit()’ is not possible. one would need to stick a (hidden) submit input/button on the form then trigger a ‘click’. unfortunately putting the hidden input on the form then breaks firefox, the jquery ‘submit()’ will no longer work(nor will triggering a ‘click’ on the button, until you remove the input/button from the iframe form.
I’ve experienced this same problem in IE as well. The problem is that IE submits the form data as soon as a form’s
submitevent is triggered, instead of propagating the event up the DOM. To rectify this, jQuery manually propagates the submit event up the DOM, and then allows the form’s own submit event to trigger.This works, unless of course you manually trigger a form’s submit event, because this causes the form to submit to the browser before bound events can fire.
The work-around I used in the Rails jquery-ujs test suite is this:
// Must indrectly submit form via click to trigger jQuery's manual submit bubbling in IE
form.find('input[type=submit]')
.trigger('click');
} else {
form.trigger('submit');
}
i am trying a similar approach, but to no avail i still am stuck with either putting the button on the form or not. in IE i have to have the button on the form for anything to happen, but then Fire fox does not work. if i remove the button fire fox works again and IE does not.
var iframe = $(window.imageiframe.document.getElementsByTagName("body")[0]);
($("#uuid", iframe)[0]).value = data.uuid;
//somehow the form element is NOT in the DOM. i have to go digging for the button.
$(window.imageiframe.submit).trigger("click");
}
else{
var imgForm = $("iframe#imageiframe").contents().find("form");
$("#uuid", imgForm)[0].value = data.uuid;
imgForm.trigger('submit');
}
so, in IE the form element in the iframe is NOT in the dom. so i need to put a button on the form to ‘click’ it. BUT when the button is on the form then in FF and chrome doing a jquery submit() on the form element(it is locateable in the DOM in these browsers)
figured out my problem, seems jquery is a little fussy about finding things in an Iframe.
apparently using
will not find the form, where as
will. i changed that and now the form will submit.
Hi ,
I have s:file upload tag in my jsp.and one sx:autocompleter tag.
if i change the element in sx:autocompleter the ajax to work.
But the s:file tag is preventing not to work ajax properly.
Can you give me some idea.Pls…..
Im trying this for past one week…..
Thanks in advance.
I’m not sure what library you’re using for your ajax file uploading, you might consider posting in the support forums or bug-tracker for that project.
the response html text for ajax submit is coming in encripted format when we are uploading file using ajaxsubmit.
can any one help me in this
Thanks for this article, it is really well made