Chapter 9. Form Elements and Validation

9.0. Introduction

Outside of hypertext links, form elements were the very first form of interaction between web developers and web page readers, and were also one of the first reasons for interest in a scripting language.

With the advent of JavaScript, form elements could be validated before the data was sent to the server, saving the reader time and the website extra processing. JavaScript can also be used to modify form elements based on the data the web reader provides, such as filling a selection list with the names of cities when a certain state is selected.

The important point to remember when using JavaScript with form elements is that people turn JavaScript off, so it can’t be a dependency for any form action. JavaScript enhances, not replaces.

Note

Though validating the form on the client can save a round trip, as a sound design practice, you’ll still want to validate the data on the server.

9.1. Accessing Form Text Input Values

Problem

You need to access the contents of a text input form element using JavaScript.

Solution

Use the DOM to access the form element:

var formValue = document.forms["formname"].elements["elementname"].
value;

Discussion

Forms in a web page can be accessed through an object collection (forms) via the document object. Each form has its own collection (elements) of form elements.

Accessing the form element’s value varies, based on the type of form element. For instance, a text input or textarea form element’s value can be accessed using the value attribute. To access the following form input element’s data:

<form id="textsearch">
<input type="text" id="firstname" />
</form>

use the following JavaScript:

txtValue = document.forms["textsearch"].elements("pattern"].value;

As demonstrated earlier in the book, you can also access the input form element directly, via its identifier:

var txtValue = document.getElementByid("pattern").value;

However, when you’re working with a larger form, you’re more likely going to want to work with the DOM Level 0 form collections, in order to be consistent.

You can also access the form element using an integer representing the form and the element’s position in the page. The first form that appears in the page is given an array index of zero, the second an index of one, and so on. The same with the elements. So to access the example form element, use the following:

var txtValue = document.forms[0].elements[1].value;

Using an array index is tricky, since it may be difficult to determine the location of a specific form and element. In addition, adding a new form or element can make for incorrect JavaScript, or a web page application that doesn’t perform as expected. However, it’s also a simple way to process all form elements in a loop:

while (var i = 0; i < document.forms[0].elements.length; i++) {
   var val = document.forms[0].elements[i].value;
}

Note

Whenever you’re accessing data from a text or other field where the user can input whatever value he wants, before sending to the database or displaying in the page, you will want to strip or encode any harmful SQL, markup, or script that may be embedded in the value. You can use encodeURI and encodeURIComponent in JavaScript for encoding.

See Also

See Recipe 2.4 for another demonstration of accessing a form and elements.

The solution uses the ECMAScript Binding for the DOM Level 2 HTML API, which can be found at http://www.w3.org/TR/DOM-Level-2-HTML/ecma-script-binding.html. For more on how to secure form input and the challenges of input security, read Jeff Atwood’s “Protecting Your Cookies: HttpOnly”.

9.2. Dynamically Disabling and Enabling Form Elements

Problem

Based on some action or event, you want to disable, or enable, one or more form elements.

Solution

Use the disabled property to enable or disable form element(s), accessing the element via the forms/elements collections:

document.forms["formname"].elements["elementname"].disabled=true;

or via direct access to an element with an identifier:

document.getElementById("elementname").disabled=true;

Discussion

It’s not unusual to disable some fields in a form until information is provided or a specific event occurs. An example would be clicking a radio button enabling or disabling other form elements, such as input text fields.

See Also

See Recipe 9.4 for an example of clicking radio buttons and enabling or disabling specific form elements. See Recipe 9.8 for another approach to providing access to form elements based on activity (hiding or showing form elements).

9.3. Getting Information from a Form Element Based on an Event

Problem

You need to access information from a form element after an event.

Solution

Depending on the form element, you can capture any number of events, and based on the event, process the form element data.

If you want to validate a form field after the data in the field has changed, you can assign a function to the onchange event handler function for the element:

document.getElementById("input1").onchange=textChanged;

In the related function, access the form element’s value:

var value = this.value;

You can also attach a function to a form element based on whether it gets or loses focus, using the onfocus and onblur event handlers. The onblur event handler can be handy if you want to ensure a form field has data:

document.getElementById("input2").onblur=checkValue;

In the function that checks to ensure some value is provided, you’ll first need to trim any whitespace. Since the String trim method is not supported in IE8, the following code uses a variation of the regular expression String.replace method covered in Chapter 2:

var val = this.value;
val = val.replace(/^\s\s*/, '').replace(/\s\s*$/, '')
if (val.length == 0) alert("need value!");

You can capture keyboard events for form elements, such as onkeypress for a checkbox, but a click event is triggered for most form elements whether the element is clicked on by a mouse or the spacebar is clicked when the element has keyboard focus:

document.getElementById("check1").onclick=getCheck;

In the function, you can then access the checkbox checked property:

var checked = this.checked;
if (checked) { ...}

Discussion

There are several different events based on the type of form element. Each can be captured, and the appropriate event handler assigned a function.

Table 9-1 contains a list of form elements and the events most commonly captured for the element.

Table 9-1. Form elements and commonly occurring events

Elements

Events

button, submit

click, keypress, focus, blur

checkbox

click, keypress

radiobutton

click, keypress

textarea

select, change, focus, blur, click, keypress, mousedown, mouseup, keydown, keyup

password, text

change, focus, blur, keypress, select

selection

change, focus, blur

file

change, focus, blur

The list of elements isn’t exhaustive, nor is the list of events, but this gives you an idea of the more commonly occurring form element/event pairings.

In the form event handler function, you can access both the event and the element to get information about both. How you do this depends on your browser, and also how you assign the events.

For instance, if you use the DOM Level 0 event handling in which you assign the event handler function directly to the event handler property:

document.getElementById("button1").onclick=handleClick;

In all browsers, you can access the element using the element context this. However, if you use DOM Level 2 and up event handling, such as the following function, which provides cross-browser event handling:

function listenEvent(eventObj, event, eventHandler) {
   if (eventObj.addEventListener) {
      eventObj.addEventListener(event, eventHandler,false);
   } else if (eventObj.attachEvent) {
      event = "on" + event;
      eventObj.attachEvent(event, eventHandler);
   } else {
      eventObj["on" + event] = eventHandler;
   }
}

You can access the element context with this for Firefox, Opera, Chrome, Safari, but not for IE8. For IE8, you’ll have to access the element using the event object:

function handleClick(evt) {
   // cross browser event access
   evt = evt || window.evt;

   // cross browser element access
   var elem;
   if (evt.srcElement)
      elem = evt.srcElement;
   else
      elem = this;

See Also

See Chapter 2 for using regular expressions with form elements, and Chapter 7 for more on event handling, including with form elements.

9.4. Performing an Action When a Radio Button Is Clicked

Problem

You want to perform an action based on which radio button is clicked.

Solution

Attach an onclick event handler to each of the radio buttons; in the event handler function, perform whatever action you need:

window.onload=function() {
  var radios = document.forms[0].elements["group1"];
  for (var i = 0; i < radios.length; i++)
    radios[i].onclick=radioClicked;
}

function RadioClicked() {
    if (this.value == "one") {
      document.forms[0].elements["line_text"].disabled=true;
   }
}

Discussion

One relatively common use for JavaScript is to modify form elements based on actions taken elsewhere in the form. For example, clicking a specific radio button may disable some elements, but enable others. To do this, you need to assign an event handler function to a form element’s event handler, and then find out information about the element that received the event.

In the solution, a set of radio buttons with the name of group1 are accessed from the form, and the onclick event handler for each is assigned to a function named functionRadioClicked. In the function, properties associated with the clicked radio button are accessible via this, which is a proxy for the owner of the event. Via this, we can find out information about the event’s owner, including the type of element receiving the event (“radio”), the tagName (“input”), and the value (“one”).

With this information, we can determine which of the radio buttons was clicked, and perform whatever action we need based on this information.

One action associated with radio buttons is to enable or disable other form elements when one or another of the radio buttons is clicked. Example 9-1 shows a more complete demonstration of this type of activity. In the example, three radio buttons are paired with three text input fields. All three text input fields are disabled when the web page is loaded. Clicking any one of the radio buttons enables one input field and disables the other two.

Example 9-1. Disabling/enabling input elements based on a clicked radio button
<!DOCTYPE html>
<head>
<title>Radio Click Pick</title>

<style>
:enabled {
    border: 4px solid #ff0000;
    padding: 5px 5px 5px 15px;
}

:disabled {
    border: 2px solid #cccccc;
}

</style>
<script>

window.onload=function() {

  // first, disable all the input fields
  document.forms[0].elements["intext"].disabled=true;
  document.forms[0].elements["intext2"].disabled=true;
  document.forms[0].elements["intext3"].disabled=true;

  // next, attach the click event handler to the radio buttons
  var radios = document.forms[0].elements["group1"];
  for (var i = [0]; i < radios.length; i++)
    radios[i].onclick=radioClicked;
}
function radioClicked() {

  // find out which radio button was clicked and
  // disable/enable appropriate input elements
  switch(this.value) {
    case "one" :
       document.forms[0].elements["intext"].disabled=false;
       document.forms[0].elements["intext2"].disabled=true;
       document.forms[0].elements["intext3"].disabled=true;
       break;
    case "two" :
       document.forms[0].elements["intext2"].disabled=false;
       document.forms[0].elements["intext"].disabled=true;
       document.forms[0].elements["intext3"].disabled=true;
       break;
    case "three" :
       document.forms[0].elements["intext3"].disabled=false;
       document.forms[0].elements["intext"].disabled=true;
       document.forms[0].elements["intext2"].disabled=true;
       break;
  }

}

</script>
</head>
<body>
<form id="picker">
Group 1: <input type="radio" name="group1" value="one" /><br />
Group 2: <input type="radio" name="group1" value="two" /><br />
Group 3: <input type="radio" name="group1" value="three" /><br />
<br />
<input type="text" id="intext" />
<input type="text" id="intext2"  />
<input type="text" id="intext3"  />
</form>
</body>

The nonassociated text input fields are disabled with each new clicked event, in order to clear previous activity. In addition, to add a little flair to the example, new CSS3 functionality to style enabled and disabled attributes is used in the example, as shown in Figure 9-1. The CSS3 setting works with all of the book target browsers except IE8.

Modifying a form element based on a radio button click
Figure 9-1. Modifying a form element based on a radio button click

See Also

See Recipe 9.2 for more information about attaching event handlers to form elements, and getting information from the elements in an event handler function.

9.5. Checking for a Valid Phone Number

Problem

You want to validate form information that requires a certain format, such as a valid phone number.

Solution

Access the form field value, and then use a regular expression to validate the format. To validate a U.S.-based phone number (area + prefix + digits):

// filter out anything but numbers to
// standardize input
var phone = document.forms[0].elements["intext"].value;
var re = /\D+/g;
var cleanphone = phone.replace(re,"");

// check length
if (cleanphone.length < 10) alert("bad phone");

Discussion

To validate form fields, you need to strip out any extraneous material first, and then test only what is necessary. Phone numbers can be provided using different formats:

  • (314) 555-1212

  • 314-555-1212

  • 314.555.1212

  • 3145551212

All you really need are the numbers; everything else is just syntactic sugar. To validate a phone number, strip out anything that isn’t a number, and then check the length, as shown in the solution.

Once validated, you can then reformat into a standard format, though usually if you’re going to store a phone number in a database, you want to store it in the smallest form possible (all numbers).

Another way to ensure that the data is correct is to provide three fields for the number, and only allow the number of characters for each field (3-3-4). But it’s probably simpler for you and for your users to use just one field.

See Also

There are any number of regular expression formulas that work for various validation purposes. See more on regular expressions in Chapter 2. Also note that many JavaScript frameworks and libraries provide simple-to-use validation routines, where all you have to do is give each input element a class name or some other indicator in order to trigger proper validation.

See Recipe 14.2 for integrating accessibility into your forms using ARIA.

9.6. Canceling a Form Submission

Problem

You want to cancel a form submission if you find the data entered into the form fields invalid.

Solution

If the form fields don’t validate, cancel the form submission event using the technique appropriate to the event handling technique you’re using. Here, we’re borrowing from Chapter 7 (where we covered events):

// listen to an event
function listenEvent(eventObj, event, eventHandler) {
   if (eventObj.addEventListener) {
      eventObj.addEventListener(event, eventHandler,false);
   } else if (eventObj.attachEvent) {
      event = "on" + event;
      eventObj.attachEvent(event, eventHandler);
   } else {
      eventObj["on" + event] = eventHandler;
   }
}


// cancel event
function  cancelEvent (event) {
   if (event.preventDefault) {
      event.preventDefault();
   } else {
      event.returnValue = false;
   }
}

window.onload=function() {
   var form = document.forms["picker"];
   listenEvent(form,"submit",validateFields);
}

function validateFields(evt) {
   evt = evt ? evt : window.event;
   ...

   if (invalid) {
      cancelEvent(evt);
   }
}

Discussion

In the same function you use to validate the form field(s), cancel the event. In the event function, the cancelEvent function checks to see if the preventDefault method is supported. If it is, it’s called. If not, the event’s returnValue is set to false (cancel event).

See Also

See Chapter 7 for more information on event handling.

9.7. Preventing Duplicate Form Submissions

Problem

Potential harm could occur if a user submits the same form multiple times. You want to prevent duplicate form submissions.

Solution

One approach is to provide a message that the form has been submitted, and then provide some means to prevent the web page reader from submitting the form again. In its simplest variation, the following would work:

function validateSubmission(evt) {
...
alert("Thank you, we're processing your order right now");
document.getElementById("submitbutton").disabled=true; // disable

Discussion

Multiple concurrent form submissions are one of the worst problems that can occur in a user interface. Most people would be unhappy if, say, they found they had purchased two of the same item when they were only expecting to purchase one.

There are several different approaches you can take to prevent duplicate form submissions, and how strict you want to be depends on the seriousness of the double submission.

For example, comment forms don’t usually restrict form submission. Duplicate submissions may result in a message that duplicate comments have posted, and the first has been rejected. Even if the duplicate comment is posted, it’s a minor nuisance, rather than a serious problem.

However, it’s essential to prevent duplicate form submission with any kind of storefront, and anything that could result in unexpected charges to your web customers.

If you do restrict duplicate form submissions, provide some form of feedback to the customer. In the solution, I took a simple approach, popping up a message providing feedback to the users that the form has been submitted, and then disabling the submit button so they can’t click it again.

It’s an OK approach, but we can take the security a little further. Instead of a pop up, we can embed a message directly into the page. Instead of just disabling the submit button, we can also use a flag to doubly ensure that a submission can’t be initiated while an existing form submission is being processed. Example 9-2 demonstrates a safer way to prevent duplicate form submissions.

Example 9-2. Demonstrating prevention of duplicate form submissions
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Prevent Duplication Form Submission</title>

<style>
#refresh
{
   display: none;
   width: 200px; height: 20px;
   background-color: #ffff00;
}
</style>
<script>
//<![CDATA[

var inprocess=false;

window.onload=function() {
   document.forms["picker"].onsubmit=validateSubmit;
   document.getElementById("refresh").onclick=startOver;
}

function validateSubmit() {

  // prevent duplicate form submission
  if (inprocess) return;
  inprocess=true;
  document.getElementById("submitbutton").disabled=true;

  // for example only
  document.getElementById("refresh").style.display="block";
  document.getElementById("message").innerHTML=
"<p>We're now processing your request, which can take a minute.</p>";

  // validation stuff
  return false;
}

function startOver() {
   inprocess=false;
   document.getElementById("submitbutton").disabled=false;
   document.getElementById("message").innerHTML="";
   document.getElementById("refresh").style.display="none";
}
//--><!]]>
</script>
</head>
<body>
<form id="picker" method="post" action="">
Group 1: <input type="radio" name="group1" value="one" />
Group 2: <input type="radio" name="group1" value="two" />
Group 3: <input type="radio" name="group1" value="three" /><br />
<br />
Input 1: <input type="text" id="intext" />
Input 2: <input type="text" id="intext2"  />
Input 3: <input type="text" id="intext3"  /><br /><br />
<input type="submit" id="submitbutton" value="Send form" />
</form>
<div id="refresh">
<p>Click to reset example</p>
</div>
<div id="message">
</div>
</body>
</html>

If you load the example into a browser and click the Send Form button, it will become disabled and two new elements will display: a processing message and a button to refresh the page. The latter is included only because this is an example, as a way to reset the example.

Normally, in a form, a post-processing web page will display with a confirmation of the action and a message of thanks, or whatever is appropriate. If Ajax is used to make the update, the form can be reenabled once the Ajax processing is complete.

9.8. Hiding and Displaying Form Elements

Problem

You want to hide form elements until some event.

Solution

Surround the form elements that will be hidden with a div element:

<form id="picker" method="post" action="">
Item 1: <input type="radio" name="group1" value="one" />
Item 2: <input type="radio" name="group1" value="two" />
Item 3: <input type="radio" name="group1" value="three" /><br />
<br />
<div id="hidden_elements">
Input 1: <input type="text" id="intext" />
Input 2: <input type="text" id="intext2"  />
Input 3: <input type="text" id="intext3"  /><br /><br />
</div>
<input type="submit" id="submitbutton" value="Send form" />
</form>

Change the div’s display to none when the page loads:

window.onload=function() {

  document.getElementById("hidden_elements").style.display="none";

  //  attach the click event handler to the radio buttons
  var radios = document.forms[0].elements["group1"];
  for (var i = [0]; i < radios.length; i++)
    radios[i].onclick=radioClicked;
}

When the event to display the form elements occurs, change the div element’s display so that the form elements are displayed:

function radioClicked() {
   if (this.value == "two") {
    document.getElementById("hidden_elements").style.display="block";
   } else {
    document.getElementById("hidden_elements").style.display="none";
   }
}

Discussion

In the solution, the hidden form elements are surrounded by a div element in order to make it easier to work with them as a group. However, you could also control the display for elements individually.

The CSS display property allows you to completely remove elements from the page (display="none"), as shown in Figure 9-2. This makes it an ideal CSS property to use; the visibility property will only hide the elements, but it doesn’t remove them from the display. If you used visibility, you’d have a gap between the displayed elements and the form button.

Page with form elements removed from display
Figure 9-2. Page with form elements removed from display

In the solution, clicking the second radio button displays the input fields, as shown in Figure 9-3. Notice in the code that if you click on the first or third radio button, the display for the hidden elements is set to none, just in case it is currently displayed after a previous second radio button selection.

You always want to take into account the state of the page whenever you’re processing an event that changes the makeup of a form. If certain elements are only displayed for given form values, then any activity in the form should either check the current state of the form or just reissue either the hide or show functionality, because it doesn’t hurt to reshow a shown element or rehide one already hidden.

Page with hidden form elements displayed
Figure 9-3. Page with hidden form elements displayed

See Also

See a full-page example of the hidden/displayed form element example in Recipe 10.1 (Example 10-1).

9.9. Modifying a Selection List Based on Other Form Decisions

Problem

You want to modify the contents of a second selection list based on the choice made in a first selection list.

Solution

You have two options when it comes to modifying the contents of one selection list, based on the choice in another selection list.

The first is to query a database and build the selection list based on the choice. This is demonstrated in Recipe 18.9, which covers Ajax.

The second approach is to maintain a static copy of the second selection list options:

var citystore = new Array();
citystore[0] = ['CA','San Francisco'];
citystore[1] = ['CA','Los Angeles'];
citystore[2] = ['CA','San Diego'];
citystore[3] = ['MO','St. Louis'];
citystore[4] = ['MO','Kansas City'];
citystore[5] = ['WA','Seattle'];
citystore[6] = ['WA','Spokane'];
citystore[7] = ['WA','Redmond'];
//And use this copy to rebuild the selection list:
function filterCities() {
  var state = this.value;
  var city = document.getElementById('cities');
  city.options.length=0;

  for (var i = 0; i < citystore.length; i++) {
    var st = citystore[i][0];
    if (st == state) {
      var opt = new Option(citystore[i][1]);
      try {
        city.add(opt,null);
      } catch(e) {
        city.add(opt);
      }
    }
  }
}

Discussion

Selection lists are often built from direct database queries. To prevent the lists from being too large, they may be built based on choices in other form elements, from an Ajax-enabled query, an array, or even a hidden selection list.

As the solution demonstrates, regardless of approach, the simplest and quickest way to populate the selection list is to first set the options array list to zero, which deletes everything from the list; then go through the available option data, and based on whatever criteria, create new options with the option data and append to the empty selection list.

To see this type of functionality in action, Example 9-3 shows an entire application that incorporates the code in the solution. Clicking a state will populate the second selection list with cities for that state. A try...catch block is used when adding the new option to the selection list, because IE8 does not support the second parameter for the element’s position in the add method. If the first add method fails, the second is used.

Example 9-3. Populating a selection list
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Populating Selection Lists</title>
<script>
//<![CDATA[

var citystore = new Array();
citystore[0] = ['CA','San Francisco'];
citystore[1] = ['CA','Los Angeles'];
citystore[2] = ['CA','San Diego'];
citystore[3] = ['MO','St. Louis'];
citystore[4] = ['MO','Kansas City'];
citystore[5] = ['WA','Seattle'];
citystore[6] = ['WA','Spokane'];
citystore[7] = ['WA','Redmond'];

window.onload=function() {
  document.getElementById("state").onchange=filterCities;
}

function filterCities() {
  var state = this.value;
  var city = document.getElementById('cities');
  city.options.length=0;

  for (var i = 0; i < citystore.length; i++) {
    var st = citystore[i][0];
    if (st == state) {
      var opt = new Option(citystore[i][1]);
      try {
        city.add(opt,null);
      } catch(e) {
        city.add(opt);
      }
    }
  }
}

//--><!]]>
</script>
</head>
<body>
<form id="picker" method="post" action="">
<select id="state">
<option value="">--</option>
<option value="MO">Missouri</option>
<option value="WA">Washington</option>
<option value="CA">California</option>
</select>
<select id="cities">
</select>
</form>
</body>
</html>

If scripting is disabled in an application like this one, the best option is to hide the city selection list by default and display a button (again by default) that submits the form and populates the city selection on a second page.

See Also

See Recipe 18.9 for a demonstration of using Ajax to populate a selection list. More on the try...catch error handling in Recipe 10.4.

Get JavaScript Cookbook now with O’Reilly online learning.

O’Reilly members experience live online training, plus books, videos, and digital content from 200+ publishers.