Thursday, January 14, 2010

Adding fields to a web form: a job for closures!

In my last post, I explained how closures work. Via their magic, I can make a function to do this:

nextNumber(); //returns 1
nextNumber(); //returns 2
nextNumber(); //returns 3

The most concise way to create this in Javascript would be:

var nextNumber = (function(){var i=1; return function() {return i++;}})();

Of course, that's not very readable. See my previous post for a step-by-step introduction to closures.

Putting closures to use

One place where this trick is really handy is when you want to dynamically add fields to a web form. You need each new field to have a unique name and ID, but pulling new IDs from a database is overkill.

Say you've got a form where users can order Viking helmets. For each helmet they order, they need to specify a color and size.

Suppose your inputs look like this:

<label for="helmet1color">Helmet 1 Color</label>
<input id="helmet1color" name="helmet[1][color]"/>
<label for="helmet1size">helmet 1 Size</label>
<input id="helmet1size" name="helmet[1][size]"/>

Ideally, you'd like to add as many new inputs as a customer wants. The more helmets they order, the sooner you can afford to buy that beet farm!

So you make a Javascript function to generate new inputs:

function newHelmetInputs(i){
var inputSet = '';            
inputSet += '<label for="helmet'+ i +'color">Helmet '+ i +' Color</label>'
inputSet += '<input id="helmet'+ i +'color" name="helmet['+ i +'][color]"/>'
inputSet += '<label for="helmet'+ i +'size">Helmet '+ i +' Size</label>'
inputSet += '<input id="helmet'+ i +'size" name="helmet['+ i +'][size]"/>'
return inputSet;

But of course you've got to tell it whether you need inputs for helmet #2 or #15. Which is where a closure function comes in: it can return a new number every time it's called. Like this:

i = nextNumber();
newInputs = newHelmetInputs(i);

Slap those inputs onto the page, and you're done! Well, almost.

Completing the Picture

To show exactly how this works, I've got to get into some specifics. For my form, I'm doing the following:

1) Using jQuery on the page
2) Validating the form prior to submission with jQuery's Validate plugin
3) Processing the form submission with PHP

With that in mind, here's how I'd add the new inputs: just after the set of helmet inputs, I'd add button a link or button that says "More Pointy Helmets!", and use the following jQuery to capture the click event:

var i = nextNumber();
var newInputs = newHelmetInputs(i);
$('#helmet' + i + 'size').rules('add', {digits: true});

That takes care of adding the inputs themselves, and adding a validation rule to make sure the size is given in digits.

Finally, you may have noticed that the names I gave the inputs are written like arrays. If you're using PHP, you can process those as arrays after the form is submitted. This means that no matter how many fields are added, you'll handle them the same way. For example, if you're generating an email that lists all the helmets in this order, you could do this:

foreach ($_POST['helmet'] as $key=>$val){
  $emailbody .= 'Helmet ' . $key . ' Color: ' . $val['color'];
  $emailbody .= 'Helmet ' . $key . ' Size: ' . $val['size'];
  $emailbody .= '

Voila! Whether the user wants a solitary helmet or enough to outfit his entire shuffleboard league, this form can handle it.