e to articulate 10 principles for effective use of HTML forms.
1.
Mark required elements.
Many sites do this, but many more don't. Remember that people are always in a hurry and will bail out of your application in a heartbeat if it starts to feel like a waste of time.
2.
Attach explanations directly to form elements.
Put field-level documentation right on the form, next to or below the widget it explains. A separate help file is fine, and a hyperlink that jumps context-sensitively from the form to the relevant part of the help file is even better. But where practical, it's best to put the explanation right on the form. Remember: People are in a hurry. If you require six-character alphanumeric passwords, say so. Don't make users click on Submit to find out the hard way.
3.
Solicit feedback.
Add a
<textarea>
widget that invites users to ask questions and make comments. Then accumulate that information into a Web, mail, or news archiv
e that you can review periodically. A
mailto:
link is fine, too, but it's one click removed from the form. People are in a hurry, and the Web relentlessly encourages context-switching.
Over time, patterns emerge from these responses. Pay attention to them, and refine your feedback mechanism accordingly. To improve a business process, you have to be able to measure it. If you instrument them properly, Web-based customer-service forms can provide the raw data.
4.
Use layout and visual cues to organize elements.
Two years ago, I avoided using HTML tables because a lot of browsers handled them poorly or not at all. This time around, I formatted all our forms using tables. The new batch look nicer than the old ASCII-formatted forms, but my motivation was only partly aesthetic. Built on a grid, forms can best express the sequences and hierarchies implicit in the data they capture.
The structure that emerged from several rewrites of our current batch of forms i
s a two-column grid of name-value pairs. This design flexibly accommodates not only simple elements but also more complex clusters of elements.
Table formatting works in several ways here. The horizontal alignment of the radio buttons, located in successive rows of the nested table, binds them into a unit. The adjacency of the cells containing the credit-card radio button and its four associated widgets binds all these into another unit. Finally, a background color groups the credit-card widgets. This design (I hope) wordlessly conveys the following instructions: 1) Choose credit-card or bill-me-later; 2) if you choose credit-card, you must fill in the associ-ated fields; and 3) if you fill in any of these fields, complete them all.
5.
Develop a consistent style.
The BYTE Site is a cluster of servers that delivers a suite of applications. We took pains to standardize the look of the HTML pages dished out by the various applications, such as archive articles, search r
esults, and conference messages. But we failed to standardize the look and feel of the forms presented by these applications.
It makes sense to do so, and we will, for reasons that are as much functional as aesthetic. One instance of the design illustrated
in the screen
might work well enough, but it works better when users encounter consistent variations on the theme.
Given such standardization, you can create and leverage a data dictionary of form elements, along with associated error-handling code and messages. For example, we collect e-mail addresses on four different forms. Clearly, each ought to inherit a common widget, along with associated error-handling code.
6.
Parse forms completely and report all errors and omissions.
In order to download the JDK 1.2 beta from JavaSoft's site, I had to register. On my first try, the server rejected my form with the "Username cannot match password" message. So I backed up, corrected that, and rese
nt the form. It rejected my form again, this time with "Required address field is blank."
This all-too-common behavior irks me. The application saw all the fields I sent, and didn't send, on the first try. Why not do all the necessary griping immediately? I'd rather take care of everything at once. Finding multiple errors one at a time, by bouncing repeatedly off a server, rapidly erodes my patience.
To spare users this headache, I've settled on an error-handling technique based on the assumption that it's better to deliver all the form-processing results all the time. I've implemented the technique in Perl, but you could just as easily do it in any other Web scripting language.
It's nothing fancy. I just accumulate the complete list of errors each time I process the form. If the list isn't empty at the conclusion of the script's error-checking phase, the script passes the list to a common library routine that displays all the errors, along with associated explanations.
An unexpected benefit
of this approach was that it greatly accelerated the process of testing the error-checking code associated with our forms. In a single cycle, you can verify that a form's handler correctly reports an incorrectly confirmed password, an empty address field, and an ill-formed account number.
7.
Accept all unambiguous inputs.
When I last renewed a digital certificate on the VeriSign site, the server rejected my order form with an "Invalid credit card number" message. Why? I'd typed the number with hyphens, but the application was looking for unhyphenated input. That's just silly. Here's the line of Perl that will reduce a mixture of 16 digits, hyphens, spaces, or other junk to just 16 digits:
$num=~ s/\D*//g;
C'mon, VeriSign. You said that better service was the reason you raised your rates on digital certificates. Doesn't my extra hundred bucks buy me one line of Perl?
8.
Use short error messages linked to longer explan
ations.
As I worked through the new batch of forms, I consolidated the error messages into a dictionary of keyword/value pairs. The keywords are short phrases, such as "AccountNotActive." The values are longer explanations, such as "please activate your account first, using the activate link on any protected page." In my case, the dictionary is implemented as a Perl hash table, but you can easily achieve the same effect in any scripting language.
This method works in conjunction with a standard error-message routine shared by all form-handling scripts. When a script checks the input sent from a form, it does not report any errors directly. Instead, it builds a list containing the keywords associated with all errors found. For example:
if (length ($username) < 4)
{push (@errs,"Name Too Short");}
if ($pw1 ne $pw2)
{push (@errs,"Bad Password");}
Then, if the error list is nonempty, the script sends the list to the error-message routine, which reports errors along with their
associated explanations:
if ($#errs > 0) {
{errorMsg (@errs)}; exit; }
I like this approach for two reasons. In the source code, the error keywords bind closely to the tests that trigger them, so the code becomes self-documenting. The same keywords, appearing on the page generated by the error-message routine, are more explanatory than numeric codes would be, yet they are more easily communicated than the accompanying long explanations. And, of course, since the error-message routine is always prepared to handle a list of keywords, the system obeys principle 6: It always reports all errors and omissions.
9.
Use forms as components.
The design of the new subscriber-access part of BYTE's Web site required the capability to vary the outcome of a given interaction with a form according to the context in which the form is invoked. For example, as a subscriber to BYTE magazine, you're entitled to activate your on-line account and view complete current
articles that are otherwise shown only as abstracts. A link to the activation form appears on every abstract. Completion of the form leads back to the full version of the article that was first displayed as an abstract.
How can you achieve this effect? It's straightforward if you dynamically generate the form from a template. In this case, the link that invokes the form self-referentially encodes the URL of the article containing the link. Here's an example:
<a href="
http://www.byte.com/
perl/activate.pl?mode=form&art
=9802/sec1/art1.htm">
activate</a>.
The signal
mode=form
tells
activate.pl
to read and emit the file activate.htm, which contains the form's HTML. That file contains a placeholder for a URL to be interpolated into this form:
<input type="hidden" name="art"
value=ART>
Here's how
activate.pl
renders the form:
if ($mode eq 'form') {
open (F,'activate.htm');
while (
<F>) {
s/value=ART/value=$art/;
print $_; } close F; exit; }
The form's action attribute again names the script
activate.pl
. But the form itself contains no element named MODE. So when
activate.pl
regains control, it skips the form-generation phase and proceeds to the form-parsing phase. If there are no errors, it calls a database routine to activate your account and then issues a confirmation page. A link on that page invites you to continue reading the article that originally prompted this sequence of events. The address embedded in that link is the URL passed on the first call to
activate.pl
, which was embedded in the form as a hidden variable during the form-generation phase.
10.
Vary widget types according to context.
As soon as this problem was solved, another requirement emerged. BYTE's circulation department decided to run a circulation drive hosted on another site,
http://www.bytesub.com
. We wanted to
refer new subscribers from bytesub.com to the same activation form used by existing magazine subscribers on The BYTE Site. But the circumstances were different. An existing BYTE magazine subscriber has to enter a subscriber name and number on the activation form in order to authenticate. A bytesub.com subscriber shouldn't have to, because the ordering process on that site already has this data.
We could tell bytesub.com subscribers to write down their credentials. Then we could refer them to a byte.com activation form that would require them to enter that data. But that would be silly. Using a Web-style remote procedure call (RPC), bytesub.com need only construct the URLthat transmits this data to
activate.pl
. It took only a few tweaks to the template and the script to empower bytesub.com to make a call like this:
/activate.pl?mode=form&name=
jon&num=123
This causes byte.com to display an activation form with prefilled subscriber-name and subscriber-number fields.
This was an improvement, but it still wasn't quite right. The name and number appeared in editable input fields, yet in this context the data should only be displayed, never changed. Although most users wouldn't touch this data, it seemed wrong to invite them to do so.
The final iteration resolved this problem by varying the types of the widgets according to context. As shown
in the figure
, the template contains placeholders for two versions of the widget pair. The
mode=
signal tells the form generator which pair to include. In a byte.com context, the form presents empty input boxes. Called from bytesub.com, it prints the passed values in these same locations.
Future Directions
We've made a lot of progress this time around, but there's still plenty of room for improvement. The data dictionary remains just a good idea; we haven't rigorously decomposed all our forms to create an inventory of reusable widgets.
Another tantalizing possibility is namespace
completion. Using the replaceable-widget technique shown
in the figure
, I've prototyped a mechanism that accepts partial input in a sidebar and then returns a version of the form that swaps in a listbox containing matches resulting from a database lookup. This magical effect is the holy grail of Web-based data-collection applications. Many commercial toolkits can deliver this capability, but homegrown (and, thus, maximally customizable) implementations are well within the reach of competent scripters.
There's nothing Perl-specific about the techniques discussed here; they'll work in any scripting environment. You just need to know how HTML forms work and be willing to fluidly adjust the relationship between scripts and forms.
TOOLWATCH
JHLZip
John Leach
Internet:
http://www.easynet.it/~jhl/apps/zip/zip.html