HTML5 File API + XmlHttpRequest = SWFUpload, now what?

This content is over 14 years old. It may be obsolete and may not reflect the current opinion of the author.


So what should you do if you decided to ditch SWFUpload and embrace HTML5 solution for ajax file upload? In honor of the releases of Firefox 3.6 (with File API), I decided to just that in the GFX Firefox promotion site. (sounds perfectly reasonable to use Fx-only technology on such site, right?) Firefox now gives us the bricks (File API, binary XHR, and File drag-drop), but to build a house it still takes some work. The Mozilla Hacks demo works great but to work with the existing site I need:

  1. To create a SWFUpload-like button that opens file selection dialog instead of HTML form file input.
  2. To send the file as a ordinary file upload form – no need to change php backend to handle raw post
  3. Send the file by jQuery.ajax, firing global callbacks

I came up with these methods to solve them:

SWFUpload-like button

From the JavaScript almighty ppk we know that file input from control is almost impossible to style. The solution outlined on that page only tell us how to replace the control with images using opacity: 0 trick. In order to fit the clickable area into my 65×65-px button, I have to enlarge the control and confine it into a <div> with overflow: hidden.

The actual hard part is how to enlarge the control. It turns out in Firefox it ignores CSS specified height and width; the only thing left for you to set is font-size propriety.

Another CSS properity it ignores is the cursor. Firefox puts the “browse” button at the very right, so you might what to make sure the arrow cursor, instead of text-input cursor, shows.

Simulate form upload

On Mozilla Hacks demo, the file is sent as raw HTTP POST/PUT data. To process such input, one must craft the server-side script all over, without any upload-handling library that I’ve known of. So we go back to client-side solution: the question now effectively becomes how to implement RFC 1867 format to your (raw) post data. I’ll simply post my code snippet below – turn out to be quite self-explanatory. *

var bd = 'gfx-xhrupload-'
	+ parseInt(Math.random()*(2 << 16));
var data = '--' + bd + '\n'
	+ 'content-disposition: form-data; name="Filedata";'
	+ ' filename="' + files[0].name + '"\n'
	+ 'Content-Type: ' + files[0].type + '\n\n'
	+ ev.target.result + '\n\n'
	+ '--' + bd + '--';

Where ev.target.result is the binary string represent the file, access using FileReader object, and files is the FileList object.

Send the file via jQuery.ajax

Update: Just found an easy way to do this right instead below: overwrite xhr.send with xhr.sendAsBinary since it’s seems all right to send text data in binary mode.*

if (window.XMLHttpRequest && XMLHttpRequest.prototype.sendAsBinary) {
	XMLHttpRequest.prototype.send = function (data) {
		return this.sendAsBinary(data);
	};
}

You might think this is the easiest part, but in reality the normal $.ajax won’t work even if you set the correct Content-Type, which, according to RFC 1867, is 'multipart/form-data, boundary=' + bd.

What went wrong? The problem is jQuery hard-coded xhr.send(data) in it’s function, and it does not send binary data. I resolved the problem by actually copying jQuery code, “patch” it, and overwrite the original $.ajax function. I added a “binary” option for the purpose; the function would switch to xhr.sendAsBinary(data) if the option is set to true.

Bonus: File drag and drop

What SWFUpload doesn’t do is to make the control accepts files by droping them on it. With Firefox 3.6 you could to that, but you cannot hook the drop event on the <input> itself. Frankly speaking, the button would be to small to aim, so for the sake of better UX, a larger overlay, like the one in Mozilla Hacks demo, should be made. Do remember that hiding the overlay as soon as dragleave fires will stop drop event from firing though – took me a day to figure that out.

Also, keep in mind your transparent button will accept file dropping if the user had installed dragdropupload add-on.

Conclusion

With all these research, looks like it’s actually possible to write a SWFUpload.js without Flash. The library would support anything SWFUpload does, except cursor choices on the button. For my project, I didn’t craft a new XHRUpload() object to replace SWFUpload() because I don’t need that much of functionality, yet it’s something can be done.

It struck me that HTML 5 is practically involves around what Google want to do things w/o Flash. StreetView? We got WebGL. GMail attach. upload? Got File API.

HTML 5. Exciting new world.

For complete code on the implementation, stay tuned and check code here once it went live. For the site, we have not deliver English version yet, but feel free to play around with buttons and symbols – you should be able to understand it without reading any Chinese.

Note: According to the MIME spec, the filename should be encoded according to RFC 1522 (“quoted-printable”) if it contains characters other than US-ASCII. Personally I replace these characters with underlines instead – name = name.replace(/[^\x20-\x7E]/g, '_');.

More update: XMLHttpRequest.prototype.sendAsBinary instead of (new XMLHttpRequest()).sendAsBinary