JavaScript: Form Validation
Form Validation
Table of Contents:
- The
scenario
- The
requirements
-
The solution (without JavaScript)
-
The solution (with JavaScript)
-
Improving the solution
-
Modifying the example for your own use
When a user enters data into the fields of a form in your Web page, it\\'s
entirely possible that the data might not make sense. The user may enter a
numeric value that is too low or too high. The user may enter text where you
expect a number, or vice versa. The user may misspell text. The user may skip a
required field. There are probably as many ways to enter invalid data as there
are ways to write forms.
It should not come as a surprise, then, that an important step in creating a
form is making sure that what the user entered makes sense. That step is called
form validation.
Creating a form that validates input-regardless of whether you use
JavaScript-requires a CGI (Common Gateway Interface) program on the server. The
Web page is only a front end to the server.
Suppose your boss has asked you to create a Web page from which customers can
order computer equipment. You need to collect the customer\\'s name, address,
phone number, age, credit card information, and what the customer wants to
order.
The customer must have a first and last name; a middle initial is optional.
The customer must have a street address. The customer must reside in the United
States and must provide either a 5-digit zip code or a 5+4 zip code. The
customer must supply a daytime phone number, including area code. The customer
must provide his or her age, and must be at least 18. The customer must supply a
credit card number, credit card type (Visa or MasterCard), and date of
expiration.
The customer may order any of the following: HAL-470 computer (,000),
Banana9000 computer (,000), high-resolution monitor (0), low-resolution
monitor (), deluxe keyboard (0), regular keyboard (), laser printer
(,000), inkjet printer (0), dot-matrix printer (0), mouse (0),
trackball (5), or scanner (0). The customer may not order more than ,000
worth of equipment.
To create a Web page form, you must first choose the kinds of input fields
you need. To capture the customer\\'s name, INPUT TEXT elements are the obvious
choice. Let\\'s make the first and last names 20 characters each and the middle
initial 1 character.
The address is next. A TEXTAREA element, 2 rows of 30 characters each, will
suffice for the street address, and INPUT TEXT elements will work for the city,
state, and zip codes-20 characters for the city, 2 characters for the state, and
10 for the zip code.
For the customer\\'s daytime phone number, use three INPUT TYPE elements-three
characters for the area code, three characters for the office code, and four
characters for the remainder of the phone number.
A three-character INPUT TEXT element will suffice for the customer\\'s age-we
could get an order from someone older than 99.
The credit card information is easy: Four INPUT TEXT elements for the credit
card number, four characters each; an INPUT RADIO element to select between Visa
and MasterCard; and two INPUT TEXT elements for the expiration date, two
characters each.
You\\'ll need a SELECT element for the merchandise, and it has to allow for
multiple selections.
Finally, you\\'ll need an INPUT SUBMIT element to send the data to the server,
and an INPUT RESET element to allow the customer to start over.
Listing 7.1: Non-JavaScript solution
The rest of the solution resides on the server, and is beyond the scope of
this book. There will be a CGI (Common Gateway Interface) program on the server
that will process the results when the customer presses the Ship It! button.
That program will have to perform form validation and return an error to the
customer if mandatory fields are omitted or mistyped. That is in addition to
whatever work must be performed to process a valid order.
Using JavaScript, you can enhance the form shown in Figure 7.1 to perform
much of the form validation on the customer\\'s browser. The advantages of doing
so are
- Better performance for the customer. Data entry errors will be caught
much more quickly by the JavaScript code than by the CGI program on the
server. (It takes time to transmit the data to the server, and CGI programs
are not, as a rule, any faster than JavaScript programs.) Feedback to the
customer is nearly instantaneous. If there are no errors, the CGI program,
which now has less error-checking to do, will process the order more
quickly.
- Better performance for the server. Because more errors are caught
locally, there is less traffic to and from the server. The CGI program does
not have to be as complicated and should execute more quickly, which
decreases the load on the server. The system can handle more orders than it
could when the server CGI program had to proofread the forms.
So what can you do to add form validation to this Web page? Plenty.
First, get rid of the SUBMIT button. You can write an event handler to catch
the submit event, and that event handler can prevent submission of the form.
However, whether the customer\\'s data is transmitted or not, the page will be
cleared. If you make the SUBMIT button an ordinary BUTTON INPUT element and give
it an ONCLICK event handler, you\\'ll have better control over the screen. All the
ONCLICK event handler has to do is perform the form validation and, if all the
customer data is acceptable, call document.forms[0].submit().
Having created an event handler to perform forms validation, let\\'s start with
the customer\\'s name. Recall that the customer must supply a first and last name.
To verify this, you simply need to check that the fields have a nonzero length,
like this:
function nameOK() { if (document.forms[ Ø ].FirstName.value.length == Ø) { alert("I need a first name, please"); return false; } if (document.forms[ Ø ].LastName.value.length == Ø) { alert("I need a last name, please"); return false; } return true; }
Then, in your "submit" button event handler, call the function:
function checkForm() { if (nameOK() == false) { return; } // other tests... document.forms[ Ø ].submit(); }
The age field has to be filled in, and the age must be a number greater than
or equal to 18. First, verify that the customer has entered anything. Then
verify that the customer has entered a number: Use the substring method to get
the first character in the field and verify that it is a digit. For that, you
need a little routine:
function isDigit(c) { var test = "" + c; if (test == "Ø" || test == "1" || test == "2" || test == "3" || test == "4" || test == "5" || test == "6" || test == "7" || test == "8" || test == "9") { return true; } return false; }
Finally, having verified that the customer entered something that starts with
a digit, use the built-in function parseInt() to convert the customer\\'s input
and verify that the value is at least 18. Putting these steps together, you have
something like this:
function ageOK() { if (document.forms[ Ø ].Age.value.length == Ø) { alert("Sorry, you have failed to enter an age"); return false; } var c = document.forms[ Ø ].Age.value.substring(Ø, 1); if (isDigit(c) == false) { alert("Sorry, you have failed to enter an appropriate age"); return false; } var result = parseInt(document.forms[ Ø ].Age.value, 1Ø); if (result < 18) { alert("Sorry, you have failed to enter an appropriate age"); return false; } return true; }
As with NameOK(), you then add this to the checkForm() function:
function checkForm() { if (nameOK() == false) { return; } if (AgeOK() == false) { return; } // other tests... document.forms[ Ø ].submit(); }
Now that the customer\\'s name and age have been verified as present and
acceptable, it\\'s time to verify the address. Verifying the presence of the
street address, city, and state fields is exactly like verifying the presence of
the first and last name fields: Just check for a nonzero length of the field\\'s
value property. The zip code is a little more interesting, however. Recall that
the zip code must be either a five-digit code or ZIP+4-5 digits, a hyphen, and
four digits.
Because this is something you\\'re going to do more than once, let\\'s write a
function to verify that a given string is composed entirely of digits. It should
simply extract the characters from the string one by one and verify that each
one is a digit:
function isAllDigits(s) { var test = "" + s; for (var k = Ø; k < test.length; k++) { var c = test.substring(k, k+1); if (isDigit(c) == false) { return false; } } return true; }
Then checking the zip code field is easy. First, check the length of the
field value-it should be either 5 or 10. If the length is 5, call isAllDigits()
for the value. If it\\'s 10 digits, use the substring method to get the first 5
characters and verify that they\\'re all digits. If they are, check the next
character-it should be a hyphen-and then use the substring method to get the
last four characters. Check that they\\'re all digits as well. The function should
look like this:
function zipOK() { var zip = document.forms[ Ø ].ZIPCode.value; if (zip.length == 5) { var result = isAllDigits(zip); if (result == false) { alert("Invalid character in zip code"); } return result; } else if (zip.length == 1Ø) { var result = isAllDigits(zip.substring(Ø,5)); if (result == true) { if (zip.substring(5,6) != "-") { result = false; } else { result = isAllDigits(zip.substring(6,1Ø)); } } if (result == false) { alert("Invalid character in zip code"); } return result; } else { alert("Invalid zip code; please re-enter it"); return false; } }
Checking the phone number is now very easy. You simply verify that all three
fields are filled in and that they\\'re all digits.
Checking the credit card numbers is also easy; just verify that there are
four digits in each of the four fields. But you can take it a step further. Most
credit card numbers can be validated by using a rule called MOD 10, and for a
given company, there is usually a fixed prefix or range of prefixes for the card
number. All Visa card numbers begin with 4, and all MasterCard card numbers
begin with 51, 52, 53, 54, or 55, and both numbers can be validated with the MOD
10 rule.
The MOD 10 rule says to scan the card number from right to left. Starting
with the digit on the right, double every other digit as you move to the left.
For the number 49927398716, this means you wind up with these numbers: 4 (9+9) 9
(2+2) 7 (3+3) 9 (8+8) 7 (1+1) 6, or 4 18 9 4 7 6 9 16 7 2 6. Doubled numbers
that become 2-digit values are then replaced with the sum of the digits: 4 (1+8)
9 4 7 6 9 7 (1+6) 2 6, or 4 9 9 4 7 6 9 7 7 2 6. These digits are then added up;
the sum should be evenly divisible by 10 (in this case, they add up to 70-the
number passes).
Applying the MOD 10 rule to our credit card numbers means that the first and
third digits of each set of four digits needs to be doubled. Rather than
manipulate the digits of the result, you can write a function to get the doubled
and digit-summed value of a given digit:
function doubleForMod1Ø(c) { var d = Ø + c; if (d == Ø) return Ø; if (d == 1) return 2; if (d == 2) return 4; if (d == 3) return 6; if (d == 4) return 8; if (d == 5) return 1; // 5+5 = 1Ø; 1+Ø = 1 if (d == 6) return 3; // 6+6 = 12; 1+2 = 3 if (d == 7) return 5; // 7+7 = 14; 1+4 = 5 if (d == 8) return 7; // 8+8 = 16; 1+6 = 7 return 9; // (digit must be 9) 9+9 = 18; 1+8 = 9 }
Then, for the entire four digits, obtain the sum:
function sumForMod1Ø(s) { var v = parseInt(s, 1Ø); // get the value var result = doubleForMod1Ø(Math.floor(v / 1ØØØ)); v = v % 1ØØØ; result += Math.floor(v / 1ØØ); v = v % 1ØØ; result += doubleForMod1Ø(Math.floor(v / 1Ø)); v = v % 1Ø; result += v; return result; }
Putting all this together, let\\'s validate the credit card number using the
code shown in Listing 7.2.
Listing 7.2: The validateCreditCardNumber() function
function validateCreditCardNumber() { if (document.forms[ Ø ].CreditCardNumber1.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber2.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber3.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber4.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber1.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber2.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber3.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber4.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (document.forms[ Ø ].CreditCardType[ 1 ].checked == true) // Visa { if (document.forms[ Ø ].CreditCardNumber1.value.substring(Ø,1) != "4") { alert("The credit card number is not valid; please re-enter it"); return false; } } else // must be MasterCard { var prefix = parseInt(document.forms[Ø].CreditCardNumber1.value.substring(Ø,2)); if (prefix < 51 || 55 < prefix) { alert("The credit card number is not valid; please re-enter it"); return false; } } var sum = sumForMod1Ø(document.forms[ Ø ].CreditCardNumber1.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber2.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber3.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber4.value); if (sum % 1Ø != Ø) { alert("The credit card number is not valid; please re-enter it"); return false; } return true; }
Verifying the expiration date is straightforward. Make sure the month and
year fields are filled in (make sure they have nonzero lengths). Make sure month
is a value from 1 to 12. Make sure month and year are at least a month in the
future. Listing 7.3 shows how it works.
Listing 7.3: The dateOK() function
function dateOK() { if (document.forms[ Ø ].ExpirationMonth.value.length == Ø) { alert("You must fill in the expiration date"); return false; } if (isDigit(document.forms[ Ø ].ExpirationMonth.value.substring(Ø,1)) == false) { alert("Expiration date should be numeric"); return false; } var eMonth = parseInt(document.forms[ Ø ].ExpirationMonth.value, 1Ø); if (eMonth < 1 || 12 < eMonth) { alert("Expiration date is out of range"); } if (document.forms[ Ø ].ExpirationYear.value.length == Ø) { alert("You must fill in the expiration date"); return false; } if (isDigit(document.forms[ Ø ].ExpirationYear.value.substring(Ø,1)) == false) { alert("Expiration date should be numeric"); return false; } var eYear = parseInt(document.forms[ Ø ].ExpirationYear.value, 1Ø); if (eYear < 5Ø) { eYear += 2ØØØ; } else { eYear += 19ØØ; } var today = new Date(); // get today\\'s date var thisYear = 19ØØ + today.getYear(); var thisMonth = 1 + today.getMonth(); if (eYear < thisYear) { alert("Your credit card seems to have expired"); return false; } if (thisYear < eYear) { return true; } if (eMonth < thisMonth) { alert("Your credit card seems to have expired"); return false; } if (thisMonth < eMonth) { return true; } alert("Your credit card has expired or is about to expire"); return false; }
Finally, the last item: the merchandise selected. You\\'ll need to make one
minor modification to the OPTION elements and give them values so you can add
them up. Then it\\'s simply a matter of walking the list of options and tallying
up the result. While you\\'re checking for a result in excess of ,000, you
should also say something if the customer didn\\'t order anything:
function merchandiseOK() { var tally = Ø; var optionCount = document.forms[ Ø ].Merchandise.options.length; for (var k = Ø; k < optionCount; k++) { if (document.forms[ Ø ].Merchandise.options[ k ].selected == true) { tally += parseInt(document.forms[ Ø ].Merchandise.options[ k ].value, 1Ø); if (5ØØØ < tally) { alert("Sorry, we cannot handle a transaction in excess of ,ØØØ.ØØ"); return false; } } } if (tally == Ø) { alert("Sorry, you don\\'t seem to have ordered anything"); return false; } return true; }
Listing 7.4 shows the entire solution, using JavaScript. There is no separate
figure for the JavaScript solution, because as far as the screen is concerned,
there are no differences.
Listing 7.4: JavaScript solution for form validation
/*function nameOK() { if (document.forms[ Ø ].FirstName.value.length == Ø) { alert("I need a first name, please"); return false; } if (document.forms[ Ø ].LastName.value.length == Ø) { alert("I need a last name, please"); return false; } return true; }
function isDigit(c) { var test = "" + c; if (test == "Ø" || test == "1" || test == "2" || test == "3" || test == "4" || test == "5" || test == "6" || test == "7" || test == "8" || test == "9") { return true; } return false; }
function ageOK() { if (document.forms[ Ø ].Age.value.length == Ø) { alert("Sorry, you have failed to enter an age"); return false; } var c = document.forms[ Ø ].Age.value.substring(Ø, 1); if (isDigit(c) == false) { alert("Sorry, you have failed to enter an appropriate age"); return false; } var result = parseInt(document.forms[ Ø ].Age.value, 1Ø); if (result < 18) { alert("Sorry, you have failed to enter an appropriate age"); return false; } return true; }
function isAllDigits(s) { var test = "" + s; for (var k = Ø; k < test.length; k++) { var c = test.substring(k, k+1); if (isDigit(c) == false) { return false; } } return true; }
function addressOK() { if (document.forms[ Ø ].StreetAddress.value.length == Ø) { alert("We need a street address"); return false; } if (document.forms[ Ø ].City.value.length == Ø) { alert("We need a city"); return false; } if (document.forms[ Ø ].State.value.length != 2) { alert("We need a 2-letter state abbreviation"); return false; } return true; }
function zipOK() { var zip = document.forms[ Ø ].ZIPCode.value; if (zip.length == 5) { var result = isAllDigits(zip); if (result == false) { alert("Invalid character in zip code"); } return result; } else if (zip.length == 1Ø) { var result = isAllDigits(zip.substring(Ø,5)); if (result == true) { if (zip.substring(5,6) != "-") { result = false; } else { result = isAllDigits(zip.substring(6,1Ø)); } } if (result == false) { alert("Invalid character in zip code"); } return result; } else { alert("Invalid zip code; please re-enter it"); return false; } }
function phoneOK() { if (document.forms[ Ø ].Phone1.value.length != 3) { alert("We need a phone number, including area code"); return false; } if (document.forms[ Ø ].Phone2.value.length != 3) { alert("We need a phone number, including area code"); return false; } if (document.forms[ Ø ].Phone3.value.length != 4) { alert("We need a phone number, including area code"); return false; } if (isAllDigits(document.forms[ Ø ].Phone1.value) == false) { alert("Bad character in phone number"); return false; } if (isAllDigits(document.forms[ Ø ].Phone2.value) == false) { alert("Bad character in phone number"); return false; } if (isAllDigits(document.forms[ Ø ].Phone3.value) == false) { alert("Bad character in phone number"); return false; } return true; }
function doubleForMod1Ø(c) { var d = Ø + c; if (d == Ø) return Ø; if (d == 1) return 2; if (d == 2) return 4; if (d == 3) return 6; if (d == 4) return 8; if (d == 5) return 1; // 5+5 = 1Ø; 1+Ø = 1 if (d == 6) return 3; // 6+6 = 12; 1+2 = 3 if (d == 7) return 5; // 7+7 = 14; 1+4 = 5 if (d == 8) return 7; // 8+8 = 16; 1+6 = 7 return 9; // (digit must be 9) 9+9 = 18; 1+8 = 9 }
function sumForMod1Ø(s) { var v = parseInt(s, 1Ø); // get the value var result = doubleForMod1Ø(Math.floor(v / 1ØØØ)); v = v % 1ØØØ; result += Math.floor(v / 1ØØ); v = v % 1ØØ; result += doubleForMod1Ø(Math.floor(v / 1Ø)); v = v % 1Ø; result += v; return result; }
function validateCreditCardNumber() { if (document.forms[ Ø ].CreditCardNumber1.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber2.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber3.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (document.forms[ Ø ].CreditCardNumber4.value.length != 4) { alert("The credit card number is not completely filled out"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber1.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber2.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber3.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (isAllDigits(document.forms[ Ø ].CreditCardNumber4.value) == false) { alert("The credit card number contains invalid characters"); return false; } if (document.forms[ Ø ].CreditCardType[ 1 ].checked == true) // Visa { if (document.forms[ Ø ].CreditCardNumber1.value.substring(Ø,1) != "4") { alert("The credit card number is not valid; please re-enter it"); return false; } } else // must be MasterCard { var prefix = parseInt(document.forms[Ø].CreditCardNumber1.value.substring(Ø,2)); if (prefix < 51 || 55 < prefix) { alert("The credit card number is not valid; please re-enter it"); return false; } } var sum = sumForMod1Ø(document.forms[ Ø ].CreditCardNumber1.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber2.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber3.value); sum += sumForMod1Ø(document.forms[ Ø ].CreditCardNumber4.value); if (sum % 1Ø != Ø) { alert("The credit card number is not valid; please re-enter it"); return false; } return true; }
function dateOK() { if (document.forms[ Ø ].ExpirationMonth.value.length == Ø) { alert("You must fill in the expiration date"); return false; } if (isDigit(document.forms[ Ø ].ExpirationMonth.value.substring(Ø,1)) == false) { alert("Expiration date should be numeric"); return false; } var eMonth = parseInt(document.forms[ Ø ].ExpirationMonth.value, 1Ø); if (eMonth < 1 || 12 < eMonth) { alert("Expiration date is out of range"); } if (document.forms[ Ø ].ExpirationYear.value.length == Ø) { alert("You must fill in the expiration date"); return false; } if (isDigit(document.forms[ Ø ].ExpirationYear.value.substring(Ø,1)) == false) { alert("Expiration date should be numeric"); return false; } var eYear = parseInt(document.forms[ Ø ].ExpirationYear.value, 1Ø); if (eYear < 5Ø) { eYear += 2ØØØ; } else { eYear += 19ØØ; } var today = new Date(); // get today\\'s date var thisYear = 19ØØ + today.getYear(); var thisMonth = 1 + today.getMonth(); if (eYear < thisYear) { alert("Your credit card seems to have expired"); return false; } if (thisYear < eYear) { return true; } if (eMonth < thisMonth) { alert("Your credit card seems to have expired"); return false; } if (thisMonth < eMonth) { return true; } alert("Your credit card has expired or is about to expire"); return false; }
function merchandiseOK() { var tally = Ø; var optionCount = document.forms[ Ø ].Merchandise.options.length; for (var k = Ø; k < optionCount; k++) { if (document.forms[ Ø ].Merchandise.options[ k ].selected == true) { tally += parseInt(document.forms[ Ø ].Merchandise.options[ k ].value); if (5ØØØ < tally) { alert("Sorry, we cannot handle a transaction in excess of ,ØØØ.ØØ"); return false; } } } if (tally == Ø) { alert("Sorry, you don\\'t seem to have ordered anything"); return false; } return true; }
function checkForm() { if (nameOK() == false) { return; } if (ageOK() == false) { return; } if (addressOK() == false) { return; } if (zipOK() == false) { return; } if (phoneOK() == false) { return; } if (validateCreditCardNumber() == false) { return; } if (dateOK() == false) { return; } if (merchandiseOK() == false) { return; } document.forms[ Ø ].submit(); }*/ // end of code -->
You can further improve the JavaScript solution. You could perform additional
checking for post office box addresses (hint: make a copy of the street address
and convert the copy to uppercase), because only the United States Postal
Service can deliver to a post office box. You could verify that the state is a
valid post office abbreviation (convert it to uppercase). If you\\'re really
ambitious, you can try tying the state, zip code, and area code together to see
if they work (an address in Georgia with a zip code that starts with 8 and an
area code of 919 would be a bad address, for example). A confirmation box
announcing what customers have purchased and how much they\\'re about to spend
would be a very nice touch. If the customer clicks on CANCEL, no harm done.
While you have the customer there, why not scroll additional advertising along
the status bar?
Although this is a fictitious example, the techniques used here do carry over
to real-world applications. In particular, some of the functions used in the
Java-Script solution-in particular isDigit() and isAllDigits()-are quite useful
for checking numeric output, such as the phone number and credit card number
validation in this chapter, or driver\\'s license and social security numbers. The
MOD 10 algorithm is in fact used by most major credit cards, and I\\'ve tested it
against all of my bank cards-it really works.
|