http-window-punkave-com-wp-content-uploads-2009-02-picture-81-png
February 25th, 2009

Frosted Lucky Tags, They're Magically Persistent

I've been talking about progressive enhancement lately: the idea that web sites should start from a core of straightforward, highly functional HTML and build out from there. JavaScript, CSS, etc. should enhance the experience and improve ease of use, but they are not substitutes for meaningful HTML. Once you go that road, you can forget about accessibility, search engine friendliness, simpler browsing devices... it's a long list.

But progressive enhancement isn't limited only to the browser side.

This week I faced a task we're all familiar with and none of us much enjoy: implementing a form with a file upload button.

[caption id="attachment_409" align="alignnone" width="368" caption="File upload: the dreaded browse button"]
[/caption]

There are many lousy things about the file upload button. But one of the worst is that, unlike regular form elements, if you present the same file upload button back to the user with their previous selection filled out... it doesn't work. They have to browse for the file again. Which contrasts harshly with the way other input elements behave: as long as you present the user's input back to them as the new default, they don't have to enter it again. And if you're asking the user to correct three or four things in a large form, you really, really don't want to make them re-upload their resume on every single pass.

Now before you start hollering about security, let me say that I fully understand why the upload button won't let you specify a default. In a word, it's dangerous. Black hat web developers could specify any file on the hard drive... perhaps a file that often contains passwords on most people's hard drives, for instance... and if the user didn't triple-check, the black hats would then be in possession of the user's personal information. Ouch.

What's more, even if this did work, the user would still be uploading the file over and over. Which is slow. And when it isn't slow, it's expensive. One sure way to exhaust your bandwidth allocation and be invited to step up to an exciting new level of service from your web hosting provider.

So what do we do about this? We work around it... over and over. A common pattern is to accept the file provisionally even though the rest of the form didn't validate, perhaps saving an object in a database but marking it "incomplete" until the user finishes the job correctly.

Of course, doing anything over and over is Repeating Yourself, and around here we frown on that sort of thing. I knew I wanted a real and lasting fix for the problem.

Enter Symfony 1.2 and its new approach to forms. Symfony 1.2's forms offer lovely abstractions like widgets and validators. A widget represents an input element, file elements included, while a validator is responsible for determing whether a value is acceptable. They are very simple and well-factored and completely independent of one another.

Or they were until I got hold of 'em.

Symfony's approach is to validate fields and then present the form again if it fails validation, redisplaying the same values to the user. But if one of those fields is a file input field, this fails pretty badly: the user has to select the file all over again.

My new widget, pkWidgetFormInputFilePersistent, and its partner in crime, pkValidatorFilePersistent, break a few rules for the sake of the common good. But like all superheroes, they bend the rules for a good cause. And they only do it when you're not looking.

By holding hands under the table, they are able to store the user's initial upload attempt in a temporary folder and pass forward just enough information to allow that file to be automatically reused if the user chooses not to replace the file on the next validation pass.

[caption id="attachment_408" align="alignnone" width="518" caption="File upload, first validation pass"]
http-window-punkave-com-wp-content-uploads-2009-02-picture-151-png
[/caption]

Am I breaking the abstraction rules of Symfony? Well, yes, a little. But I'm doing it in order to reinforce the "anything the user enters, you can present back to them for further editing" behavior on which Symfony's forms rely. When you substitute my file widget and validator for the regular versions, absolutely nothing changes... except that users don't have to upload the same stuff over and over again. And we've eliminated a host of oh-shit-how-do-we-fix-this-now-that-the-client-noticed workarounds in different projects, all seeking to resolve this same issue.

One notable way in which I don't cheat in this code: I take advantage of PHP's really handy support for arrays in HTML forms. Perhaps you haven't seen this trick before. When you write:

<input name="dog[name]" /> <input name="dog[breed]" />

... In your HTML, you might think it necessary to look at $_REQUEST['dog[name]'] in your PHP code. But this isn't the case. You receive an array instead, meaning that you can look at $_REQUEST['dog']['name'].

How does this help us? The apparent extreme simplicity of Symfony's new widgets and validators initially made me think there was no way to manage both the file input element itself and a hidden element with a randomly generated ID for the file you uploaded on the previous pass, in case you decide not to replace it. But in fact a Symfony validator registered for 'file' will receive an array if the corresponding widget puts out elements named 'file[input]' and 'file[persistid]'. And this works even if arrays are nested. Which means that Symfony widgets and validators actually have all of the information they need to implement complex controls.

This, then, is progressive enhancement on the server side. We're able to preserve a simple and elegant approach to handling forms by encapsulating a tricky but worthwhile enhancement in a way that plays nicely with existing Symfony code. You don't have to take a fundamentally different approach to use my persistent file upload widget in place of the regular one. It just makes things more awesome when it's there. And that's what progressive enhancement is all about.

You can check out pkPersistentFileUploadPlugin here.
Check out another article
February 23rd, 2009
A Hint
February 19th, 2009
IDES 322: Instructables
By