Extending forms

See section 8E of the book for cloning elements.

Explorer 6 Windows hase serious trouble with radio buttons. Despite different names they see all generated radio buttons as one array, so the user can only select one radio button in all the copies.

On this page I treat a very simple W3C DOM script. It serves to explain why I think the W3C DOM will allow us to see interaction design in a radically new way.

The idea

Suppose you have an online CD ranking tool. You want your users to review as many CD's as they like. However, how do you know how many CD's an average user wants to review? How many form fields should you add to the page?

Before the W3C DOM this was quite a problem. Suppose you add form fields for 7 CD's. Some users will review only one CD and don't need the rest of the form (it might even frighten them). Other users might want to add their entire CD collection of hundreds of titles to your database and have to submit the form dozens of times. This is quite annoying.

Only using the W3C DOM you can allow your users to generate as many form fields as they need. This effect is impossible to mimic with any previous JavaScript technique.

Example

Which CD's did you listen to recently?

When you hit 'Send form' the form is sent to a script that lists the parameters it has received. This is to check whether the generated fields are really sent to the server. Unfortunately it turns out that Explorer Mac and Safari don't send any fields to the server.

Problems in Explorer

Unfortunately there are two serious problems in Explorer Windows:

First of all it sees all generated radio buttons as belonging to one single array, even if they have different names. Thus the user can select only one radio button in all generated fields. Basically this means that you cannot use radio buttons at all in generated forms.

A reader said that generating radio buttons through innerHTML works fine. If you must use radio buttons, you might try this approach.

Secondly the generated form fields are unreachable by a traditional document.forms call: Explorer simply doesn't enter them in the arrays. This can be worked around by giving the form field an ID and then using getElementById().

Explanation

The HTML of the form is:

<div id="readroot" style="display: none">

	<input type="button" value="Remove review"
		onclick="this.parentNode.parentNode.removeChild(this.parentNode);" /><br /><br />

	<input name="cd" value="title" />

	<select name="rankingsel">
		<option>Rating</option>
		<option value="excellent">Excellent</option>
		<option value="good">Good</option>
		<option value="ok">OK</option>
		<option value="poor">Poor</option>
		<option value="bad">Bad</option>
	</select><br /><br />

	<textarea rows="5" cols="20" name="review">Short review</textarea>
	<br />Radio buttons included to test them in Explorer:<br />
	<input type="radio" name="something" value="test1" />Test 1<br />
	<input type="radio" name="something" value="test2" />Test 2

</div>

<form method="post" action="/cgi-bin/show_params.cgi">

	<span id="writeroot"></span>

	<input type="button" id="moreFields" value="Give me more fields!" />
	<input type="submit" value="Send form" />

</form>

The actual form fields are in a DIV with id readroot and display: none. This DIV is a template that should not be changed by the user. When the user wants more fields we clone the DIV and append this clone to the form. We do this once onLoad, so that the user sees one set of form fields when entering the page.

The DIV is outside the actual FORM so that the template fields aren't sent to the server when the form is submitted.

The span with id writeroot serves as a marker. The new sets of form fields should be inserted just before it.

Adding form fields

This script adds sets of form fields when necessary:

var counter = 0;

function moreFields() {
	counter++;
	var newFields = document.getElementById('readroot').cloneNode(true);
	newFields.id = '';
	newFields.style.display = 'block';
	var newField = newFields.childNodes;
	for (var i=0;i<newField.length;i++) {
		var theName = newField[i].name
		if (theName)
			newField[i].name = theName + counter;
	}
	var insertHere = document.getElementById('writeroot');
	insertHere.parentNode.insertBefore(newFields,insertHere);
}

window.onload = moreFields;

First of all we need a counter, because all sets of form fields should get unique names. We do this by appending counter to the names in the template. Initialize counter:

var counter = 0;

Then for the actual function. Start by increasing counter by 1.

function moreFields() {
	counter++;

Then we clone our template, remove its id and set its display to block. The id 'readroot' should remain unique in the document, and the clone of the template should be visible to the user.

	var newFields = document.getElementById('readroot').cloneNode(true);
	newFields.id = '';
	newFields.style.display = 'block';

We go through the child nodes of the clone

	var newField = newFields.childNodes;
	for (var i=0;i<newField.length;i++) {

Whenever a child node has a name we append counter to it. Thus the names of all form fields remain unique.

		var theName = newField[i].name
		if (theName)
			newField[i].name = theName + counter;
	}

Now the clone is ready to be inserted into the document. We insert it just before the span with id="writeroot".

	var insertHere = document.getElementById('writeroot');
	insertHere.parentNode.insertBefore(newFields,insertHere);
}

Finally we execute this function once onLoad so that the user will initially see one set of form fields.

window.onload = moreFields;

Removing form fields

Each clone of the template contains a 'Remove review' button:

	<input type="button" value="Remove review"
		onclick="this.parentNode.parentNode.removeChild(this.parentNode);" /><br /><br />

Clicking on it causes the button to remove its parent node (the DIV) from its own parent node (the FORM). Thus one set of form fields disappears entirely, never to return.