Forms & JavaScript Living Together in Harmony

Originally published on evolt.org

Most developers don't surf the web with JavaScript turned off on purpose (with the obvious exception of the aardvark) so they probably aren't aware of the devastating effects caused by the irresponsible use of JavaScript when working with forms. Fortunately, there are rarely instances where this lack of respect for the non-JavaScript users is necessary. By exposing you to some of the common examples of forms that are hostile to non-JavaScript users and the ways these same forms can be implemented to be friendly to non-JavaScript users, I'm hoping you'll see the wisdom in carefully planning your use of JavaScript with forms.

What are some examples of irresponsible use of JavaScript with forms?

Look Ma! No submit button!

The first and most common is the use of a dropdown menu as navigation (See Figure 1 below). Aside from the obvious usability and accessibility concerns inherent in this design faux pas, some developers unnknowingly render the navigation useless to non-JavaScript users by omitting a submit button. Often the argument for not including the submit button is that it uglies the design. I'd argue that the dropdown menu isn't much prettier, even with lots of css styling applied to it. However, most designers and some developers are vain and would rather sacrifice usability for the sake of "the look". So, non-JavaScript users suffer.

Figure 1 — Example of using dropdown menus for navigation with reckless abandon at Chevrolet.com.  (click to see a larger version)Figure 1: An example of a site that uses dropdown menus as navigation, with no apparent means for non-JavaScript users to get to the same content. You might be thinking the blue square in the top-right of the figure is a submit button. Don't let it fool ya. Just because it's sitting next to a dropdown menu doesn't mean it has anything to do with it. It's actually a link. See what I mean.

<lisp>The submit buttons must be purple and do that faaaabulous image swapping thingy like the navigation!</lisp>

The second is the gratuitous abuse of branding — colors and rollovers spilling over in to submit buttons (and all too often the form elements themselves, thanks to CSS). As if graphical submit buttons aren't bad enough, some designers practically demand the graphical submit buttons rollover when the user mouses over them (Figure 2 below is a fine example of this). Unfortunately, too many developers are stuck using wygiwyd software and/or are unaware of the possibilities available in the latest browsers that don't require neutering the form for non-JavaScript users in order to quell the unrelenting demands of the designer. If they do seek help from other developers in finding a solution, the suggestion is almost always to wrap an image with a link that calls the submit() method. Too bad that's not the solution.

Figure 2 — Example of castrating a form by forcing the user to have JavaScript enabled to submit it.  (click to see a larger version)Figure 2: An example of a site that places design element behavior over usability and accessibility. If I don't have JavaScript enabled, I can't submit the form by clicking on the "Submit" button. In Mercedes-Benz USA's favor is that you can still submit the form by pressing the ENTER key on the keyboard when the cursor is in the Zip Code field. See what I mean.

That's not how you validate a form?

There are right ways to perform form validation with JavaScript and then there are wrong ones. All too often, developers implement validation the wrong way. For JavaScript enabled users, the result is the same. However, done the wrong way, non-JavaScript users are left with a form that does absolutely nothing when they click the submit button. If you read no further, take with you the knowledge that the only ways to properly perform form validation with JavaScript is to use the onsubmit event handler of the <form> tag or the onclick event handler of a non-graphical submit button to call a function that returns a boolean of true or false.

So what's the solution?

The most obvious solution is to not use JavaScript at all. However, that usually negatively impacts a user's experience by forcing a round-trip to the server just to get the form back with error messages indicating required fields they didn't enter data or pattern-matched fields with syntactically invalid data. Besides, this article is about using JavaScript responsibly with forms — not about not using JavaScript at all.

Taming the dropdown menu

If you insist on going against evidence that using dropdowns as navigation is a bad idea, at least include a mechanism to make them usable by your non-JavaScript users. If you're at least marginally conscious of usability issues, you'll resign yourself to not using the onchange event handler to change the page. That means you'll likely have some other mechanism like a button or link that finds the user's selection and changes the page to the one they selected. Making this work for non-JavaScript users will require only a small amount of work. You'll need to change that link or button to a regular submit button. You can even use a graphical submit button if you prefer. Rather than calling the function from the submit button though, you'll need to move the call to the onsubmit event handler so this will work with the most browsers possible (Some browsers <cough>Netscape Navigator 4.x</cough> don't support onclick event handlers on graphical submit buttons). You'll also need to reference a server-side script in the action attribute that the non-JavaScript users will submit the form to. This server-side script will take the value they selected from the dropdown menu and perform a simple redirect (This article will use ColdFusion in all server-side code examples because that's what I'm familiar with.). See Listing 1 for some example JavaScript illustrating this technique. Listing 2 contains some example ColdFusion code taht could be used to facilitate that server-side portion of this solution.

Listing 1 — The HTML & JavaScript for the client-side processing 

<script language="JavaScript" type="text/javascript">
<!--
  function navigateForm(obj)
  {
    if(obj.selectedIndex != -1 && obj.options[obj.selectedIndex].value)
      top.location.href = obj.options[obj.selectedIndex].value;
    return false;
  }
//-->
</script>

<form action="navigate.cfm" method="post" onsubmit="return navigateForm(this.form.navigateSelect)">
  <select name="navigateSelect" id="navigateSelect" size="1">
    <option value="">Choose One</option>
    <option value="/">Home</option>
    <option value="/products/">Products</option>
    <option value="/faq/">FAQ</option>
    <option value="/about_us/">About Us</option>
    <option value="/contact_us/">Contact Us</option>
  </select>
  <input type="submit" value="Go" />
</form>

Listing 2 — The ColdFusion for the server-side processing 

<cfparam name="form.navigateSelect" default="/">
<cflocation url="#form.navigateSelect#" addtoken="No">

If you just can't live with the submit button being visible to your JavaScript enabled users, leave some room in the design for it and only show it to non-JavaScript users by wrapping it in a <noscript> tags. See Listing 3 below for an example of the HTML.

Listing 3 — Hiding the Submit button from JavaScript-enabled users 

<form action="navigate.cfm" method="post" onsubmit="return navigateForm(this.form.navigateSelect)">
  <select name="navigateSelect" id="navigateSelect" size="1">
    <option value="">Choose One</option>
    <option value="/">Home</option>
    <option value="/products/">Products</option>
    <option value="/faq/">FAQ</option>
    <option value="/about_us/">About Us</option>
    <option value="/contact_us/">Contact Us</option>
  </select>
  <noscript>
  <input type="submit" value="Go" />
  </noscript>
</form>

[Note: The <noscript> tags were introduced with JavaScript1.1. Therefore, any browser that doesn't support JavaScript1.1 will ignore the <noscript> tags and attempt to display anything contained within them. As the only browser this really affects is Netscape Navigator 2.x, you're not likely to experience this problem.]

Appeasing The Demanding Designer

Provided you're willing and able to make this functionality available for only a portion of your audience, you can give the design what he or she wants without crippling the form for non-JavaScript users. Don't let this notion of some visitors missing out on the foofoo eye-candy stop you from doing the right thing. Though the numbers could vary for your own site, I'm probably talking about 5% or less of your audience not getting the rollover effect the designer is so proud of. However, all of your users, despite their support for JavaScript, should be able to use the form.

Before we charge off into some example code showing how to accomplish this, let's take a look how it's usually accomplished in a manner that makes it unusable for non-JavaScript users. Listing 4 below contains HTML for a very small form.

Listing 4 — Snazzy but an obstacle for some 

<form action="/login.cfm" name="loginForm" method="post">
  <input type="text" name="username" value="" /><br />
  <input type="password" name="password" value="" /><br />
  <a href="JavaScript:document.loginForm.submit()" onmouseover="rollOver('loginButton', 'on')" onmouseout="rollOver('loginButton', 'off')"><img name="loginButton" src="images/login_off.gif" width="88" height="31" border="0" alt="Login"/></a>
</form>

So how do we remedy this situation so it's usable by all visitors to our site? Simple — remove the <a> tag and use an image submit button instead of a regular image. We'll also move the onmouseover and onmouseout event handlers to the image submit. Most (like 95+%) of the browsers that will be visiting your site will support these event handlers on the image submit. Those that don't will simply get a normal, "boring" image submit button. See Listing 5 below to see what the code in Listing 4 looks like after this conversion.

Listing 5 — Snazzy and usable too 

<form action="/login.cfm" name="loginForm" method="post">
  <input type="text" name="username" value="" /><br />
  <input type="password" name="password" value="" /><br />
  <input type="image" src="images/login_off.gif" width="88" height="31" border="0" alt="Login" onmouseover="rollOver('loginButton', 'on')" onmouseout="rollOver('loginButton', 'off')">
</form>

"Are you an idiot? Tell me you didn't just put that there." or Validating Form Data

The last one that's so often funked up by irresponsible JavaScripters is form validation. The sad thing is that they mess it up not out of spite, but because they don't understand the right way to do it. The wrong way to do it almost always involves the use of the submit() method. So, what exactly is the right way?

The right way involves building the form and its functionality from start to finish without any JavaScript. Complete all your server-side checking and processing. Then, come back and add your client-side checking to improve the user's experience. It's as simple as adding an onsubmit event handler to your <form> tag. Since this event is cancellable, we preface our validation function call with a return statement. The validation function will return a boolean value of true or false finishing off this return statement. A return true statement will result in the default behavior — the form submitting. Conversely, a return false statement will result in the form submission being aborted. See Listing 6 below for an example of how the function in the onsubmit event handler is called.

Listing 6 — How to call your validation function with the onsubmit event handler 

<form action="/login.cfm" name="loginForm" method="post" onsubmit="return validateForm(this)">
[Note: See my article on event handlers and cancelling events called "JavaScript: The Point of No Return!?" for an overview of these concepts.]

Let's take a look at our form validation function — validateForm() — to see how it works. Before we do though, it'd be a good idea to see the form we'll be working with. For simplicity, we'll be validating a simple login form with username and password fields. See Listing 7 below for the HTML for this login form.

Listing 7 — Our Login Form 

<form action="/login.cfm" name="loginForm" method="post" onsubmit="return validateForm(this)">
  <label for="username"
  ><u>U</u>sername</label> 
  <input type="text" name="username" id="username" tabindex="1" accesskey="u" value="" /><br />
  <label for="password"><u>P</u>assword</label>
  <input type="password" name="password" id="password" tabindex="2" accesskey="p" value="" /><br />
  <input type="submit" tabindex="3" accesskey="l" value="Login" />
</form>

When the user submits the form our validateForm() function is called (Follow along with the code in Listing 8 below). The function creates a couple of variables local to the function that it'll use for storing the return value (returnValue), the concatenated error message (errorMessage), and which field needs to receive focus if the form doesn't validate (focusField). Then the function checks the username and password fields for values. If either field is blank, it adds some text to the errorMessage variable, sets the focusField variable to the current field if it hasn't been set yet, and changes the returnValue variable to false. If both fields have values then returnValue keeps its initial value of true. This boolean is then returned to the calling event handler by the return statement on the last line of our function. The event handler uses this boolean value that comes back from the function in combination with the return statement just before the function call and allows submission if the boolean is true or halts submission if it is false.

Listing 8 — validateForm() 

function validateForm(form)
{
  var returnValue = true;
  var errorMessage = 'The following field(s) are required and do not contain any information:\n\n';
  var focusField = null;
  if(!form.username.value)
  {
    errorMessage += ' - Username\n';
    if(!focusField)
      focusField = form.username;
    returnValue = false;
  }
  if(!form.password.value)
  {
    errorMessage += ' - Password\n';
    if(!focusField)
      focusField = form.password;
    returnValue = false;
  }
  if(!returnValue)
  {
    alert(errorMessage);
    if(focusField)
      focusField.focus();
  }
  return returnValue;
}

In conclusion

I've only covered the most common JavaScript mistakes made when working with forms. There are lots more out in the wild that frustrate non-Javascript users to no end. I hope this article has shown that it doesn't have to be this way and encouraged you to be the best developer you can be. After all, forms and JavaScript really can live together in harmony.