Hacking a File API onto IE8

With the release of our Javascript API, we’ve had a number of people ask how in the world we were able to make a cross-browser solution for reading, writing, and storing files in javascript, especially with IE8. It’s fun stuff, so I wanted to share with the community how we did it.

Warning: what follows was painful to implement. If you don’t want to have to deal with building this in-house, use Filestack. Really, you’ll thank yourself.

Tl;dr: Rip the file input element out of the dom and into your own form. Submit it to an iframe that you’ve inserted into the frame. Read the contents server-side and pass them back via the iframe.

Limitations of IE8

It’s no IE6, but compared to the new hotness on the most recent webkit and firefox builds, working with IE8 feels like you’re trying to run with one foot tied behind your back. In our specific case, IE8 lacked several key things:

  • No support for FormData, which would allow you to send files to the server via XMLHttpRequests.
  • No support for the HTML5 File API, which would allow client-side reading of files.

Given that IE8 can’t upload files using AJAX or read files locally, how do we do it?

Async File Uploads

IFrames, everyone’s favorite work-around. The way it works is that if you submit a form with the target attribute pointing to an iframe, when the form submits the response will be loaded into an iframe rather than redirecting the original page. You can then bind an onload event to the iframe and read the contents when it’s done. This is a reasonably well-documented hack, but gets more interesting if you are coming in as a javascript include rather than the original page author.

Creating an iframe to POST to

First off, give that we’re coming in as an include, we have to set everything up ourselves in javascript. So the first thing we do on a filepicker.store call in IE8 is insert an iframe into the page that we can POST against.

//Opening an iframe to make the request to
var uploadIFrame = document.createElement("iframe");
uploadIFrame.id = IFRAME_ID;
uploadIFrame.name = IFRAME_ID;
uploadIFrame.style.display = 'none';
uploadIFrame.onerror = uploadIFrame.onload = function(){
    //so we don't lock ourselves
    running = false;
};
document.body.appendChild(uploadIFrame);

Creating a new form and copying over the file input

Now because we don’t want to post potentially sensitive data to our servers that may be in the rest of the form, we actually create a new form, put it into the dom, rip the original file input out of the original form and put it in our new form, configure the name and other parameters to match what we need, post it to our iframe, and then put it back, restore the original name, and refocus the file input. Voila.

var form = document.createElement("form");
form.method = options['method'] || 'GET';
form.action = url;
form.target = IFRAME_ID;
        
var data = options['data'];
if (fp.util.isFileInputElement(data) || fp.util.isFile(data)) {
    //For IE8 you need both. Obnoxious
    form.encoding = form.enctype = "multipart/form-data";
}

document.body.appendChild(form);

Note: For some reason, IE8 needs you to set both the encoding and enctype of the form. And yes, that is a real comment in our code.

Server Response in JSONP-like format

Once the form is submitted, we store the uploaded file into your S3 and hand back a JSON blob to the iframe.

Protip: Even though you should use application/json, if your response is of a content-type other than text/html, IE8 will prompt the user to download the response rather than putting it into the iframe.

Actually, to be fair, we can’t even do that, because pages on different domains can’t use javascript to read the contents of one another. What we return to the iframe is actually the JSON object wrapped in our own JSONP-like incantation: the server response is a script that runs window.postMessage as soon as it loads, which sends the data back to the filepicker.js library on the calling page. And with that, we have our async file uploads in IE8.

Aren’t you glad you don’t have to worry about that, you just say “store this file”, we’ll put it in your s3 and hand back a url that points to the file. I think it’s pretty cool.

Reading local files

Now that we had a way of storing files, we wanted a way to read files locally, so you could call filepicker.read() and get the contents of the file directly. In newer browsers we wrap the HTML5 File API, but in IE8 we’re not so fortunate.

Instead, we have our server read the file and send it back in a readable format. We use the procedure above to send the file to our server. Iframe, new form, and all. Then have the server read the contents, convert to base64 if needed, and send it back down to the client.

Since this is all cross-domain, we have to use the obscure and handcuffed XDomainRequest. Since XDomainRequest only supports GET, POST, to do other HTTP methods, like DELETE, you can submit a POST with {method: 'DELETE'} in the data payload.

Again, since we are an included library and want to stay lightweight, we do all of this without the help of jquery, underscore, etc.

All in all, it was a fun challenge, and it’s great to hear all the cool things the community is building with it.

To see the product in action, head over to the Filestack docs.

Discussion on HN

Read More →