Infrequently Noted

Alex Russell on browsers, standards, and the process of progress.

File uploading with Dojo

Professional's need professional tools, which is one of the main reasons we started the Dojo project. Dojo's I/O system is a prime example. From built-in caching (and cache busting) to making requests bookmarkable, the dojo.io.bind() abstraction makes doing Ajax easier than with any other client-side library. As I promised earlier I'm going to cover the memory leak prevention and file uploading support in dojo.io.bind().

As with DOM event handlers, IE gets easily confused about what it can and cannot clean up when you make XMLHTTP requests. The naive way of listening for the "finished" state in an asynchronous request is to attach a function to the XMLHTTP object's onreadystatechange slot and determine inside the handler if the desired state has been reached. The problem though is the same as with DOM event handlers; since the XMLHTTP object in IE is an ActiveX control, IEs reference counting mechanism goes walkabout when presented with an event handler that is assigned from the JavaScript context. The contexts (closures) referenced by the event handler get leaked. Oops.

After much testing, we figured out a pattern that prevents this kind of leakage. Instead of attaching a method to be called back to, Dojo's IO system starts a timer to watch for state changes on in-flight network requests. When a change is detected, we dispatch the correct event handler from outside the XMLHTTP object's event system. Since we never set a closure reference between the ActiveX object and the JavaScript context, we never leak it. Of course, once there are no more operations "in the air", we cancel the timer that watches for state changes to prevent further resource usage.

But wait! there's more!

Dojo's IO system not only handles hand-rolled requests, it will also submit entire forms in the background for you. Just pass in a reference to the form's DOM Node in the formNode parameter and it gets automatically serialized into a request of the specified variety (either POST or GET). There's no reason that adding Ajax to legacy apps should be harder than doing it in new development.

The big caveat-emptor, however, has always been uploading files. If you pass a form node to the default XMLHTTP back end for dojo.io.bind(), it will refuse to process the request since there's no portable way to get the contents of a file on disk from inside the browser environment. But forms are different. Browsers let users explicitly specify which file to upload in a form and submitting these forms carries the encoded form content along for the ride. It'd be really handy if we could treat these forms the same way in an Ajax application.

As alluded to earlier, dojo.io.bind() supports "pluggable" back-ends. When a request is passed into bind, it asks each of the registered transport systems if they will accepted the request in turn. The first one to say "yes" gets passed the request to handle. In most cases, this is the XMLHTTP transport, but not for forms with file uploads. For these occasions, Dojo provides an "IframeIO" module. Since we can "target" a form to be submitted in whatever frame we please, the IframeIO class lets the browser process the request normally, but still in the background. Using it is as simple as adding the following line to your scripts:

dojo.require("dojo.io.IframeIO");

Now, passing the formNode parameter will work for both upload and non-upload cases. The XMLHTTP transport will still get used for most requests but when presented with a file upload, your code will now handle it gracefully, albeit with that click sound in the background when the form is submitted. A small price to pay for being able to add file-upload capability to single-page applications!

Next time: uploading files...without the file?