8.2 Performing Common Text Field Validations
NN 2, IE 3
8.2.1 Problem
You want to verify that a
text box contains one of the following: any text, a number, a string
of a fixed length, or an email address.
8.2.2 Solution
Apply the library of text
field validation routines shown in the Discussion (Example 8-1 and Example 8-2) to your
form. The library includes the following functions:
- isNotEmpty( )
-
The field has one or more characters in it.
- isNumber( )
-
The value is a number.
- isLen16( )
-
The field contains exactly 16 characters.
- isEMailAddr( )
-
The field contains a likely email address.
For real-time validation of text box entries, use an
onchange event handler in the
input element and pass a reference to the element
by way of the this keyword. For example, the
following input element could be used for an email
address:
<input type="text" size="30" name="eMail" id="eMail"
onchange="isEMailAddr(this)" />
See Recipe 8.3 for an example of how these validation functions can
be linked together in batch validation prior to submitting the form.
The return values from the validation functions are vital for
successful operation triggered by the form's
onsubmit event handler.
8.2.3 Discussion
Example 8-1 shows a set of fully backward-compatible
text validation functions. All of these functions are to be invoked
by both the onchange event handler of the text box and
a batch validation function triggered by the
onsubmit event handler of the enclosing form.
All functions are passed references to the form control invoking the
event handler.
Example 8-1. Backward-compatible text field validation functions
// validates that the field value string has one or more characters in it
function isNotEmpty(elem) {
var str = elem.value;
if(str = = null || str.length = = 0) {
alert("Please fill in the required field.");
return false;
} else {
return true;
}
}
// validates that the entry is a positive or negative number
function isNumber(elem) {
var str = elem.value;
var oneDecimal = false;
var oneChar = 0;
// make sure value hasn't cast to a number data type
str = str.toString( );
for (var i = 0; i < str.length; i++) {
oneChar = str.charAt(i).charCodeAt(0);
// OK for minus sign as first character
if (oneChar = = 45) {
if (i = = 0) {
continue;
} else {
alert("Only the first character may be a minus sign.");
return false;
}
}
// OK for one decimal point
if (oneChar = = 46) {
if (!oneDecimal) {
oneDecimal = true;
continue;
} else {
alert("Only one decimal is allowed in a number.");
return false;
}
}
// characters outside of 0 through 9 not OK
if (oneChar < 48 || oneChar > 57) {
alert("Enter only numbers into the field.");
return false;
}
}
return true;
}
// validates that the entry is 16 characters long
function isLen16((elem) {
var str = elem.value;
if (str.length != 16) {
alert("Entry does not contain the required 16 characters.");
return false;
} else {
return true;
}
}
// validates that the entry is formatted as an email address
function isEMailAddr(elem) {
var str = elem.value;
str = str.toLowerCase( );
if (str.indexOf("@") > 1) {
var addr = str.substring(0, str.indexOf("@"));
var domain = str.substring(str.indexOf("@") + 1, str.length);
// at least one top level domain required
if (domain.indexOf(".") = = -1) {
alert("Verify the domain portion of the email address.");
return false;
}
// parse address portion first, character by character
for (var i = 0; i < addr.length; i++) {
oneChar = addr.charAt(i).charCodeAt(0);
// dot or hyphen not allowed in first position; dot in last
if ((i = = 0 && (oneChar = = 45 || oneChar = = 46)) ||
(i = = addr.length - 1 && oneChar = = 46)) {
alert("Verify the user name portion of the email address.");
return false;
}
// acceptable characters (- . _ 0-9 a-z)
if (oneChar = = 45 || oneChar = = 46 || oneChar = = 95 ||
(oneChar > 47 && oneChar < 58) || (oneChar > 96 && oneChar < 123)) {
continue;
} else {
alert("Verify the user name portion of the email address.");
return false;
}
}
for (i = 0; i < domain.length; i++) {
oneChar = domain.charAt(i).charCodeAt(0);
if ((i = = 0 && (oneChar = = 45 || oneChar = = 46)) ||
((i = = domain.length - 1 || i = = domain.length - 2) && oneChar = = 46)) {
alert("Verify the domain portion of the email address.");
return false;
}
if (oneChar = = 45 || oneChar = = 46 || oneChar = = 95 ||
(oneChar > 47 && oneChar < 58) || (oneChar > 96 && oneChar < 123)) {
continue;
} else {
alert("Verify the domain portion of the email address.");
return false;
}
}
return true;
}
alert("The email address may not be formatted correctly. Please verify.");
return false;
}
Regular expression versions of the validation functions are more
compact when the validation is complex (as in the case of email
addresses), but they require great care and stress testing to make
sure they are doing what you expect. Example 8-2
shows the equivalent validation functions using regular expressions.
Example 8-2. Text field validation functions with regular expressions
// validates that the field value string has one or more characters in it
function isNotEmpty(elem) {
var str = elem.value;
var re = /.+/;
if(!str.match(re)) {
alert("Please fill in the required field.");
return false;
} else {
return true;
}
}
//validates that the entry is a positive or negative number
function isNumber(elem) {
var str = elem.value;
var re = /^[-]?\d*\.?\d*$/;
str = str.toString( );
if (!str.match(re)) {
alert("Enter only numbers into the field.");
return false;
}
return true;
}
// validates that the entry is 16 characters long when
// input field's maxlength attribute is set to 16
function isLen16(elem) {
var str = elem.value;
var re = /\b.{16}\b/;
if (!str.match(re)) {
alert("Entry does not contain the required 16 characters.");
return false;
} else {
return true;
}
}
// validates that the entry is formatted as an email address
function isEMailAddr(elem) {
var str = elem.value;
var re = /^[\w-]+(\.[\w-]+)*@([\w-]+\.)+[a-zA-Z]{2,7}$/;
if (!str.match(re)) {
alert("Verify the email address format.");
return false;
} else {
return true;
}
}
Notice that the validation done in these functions provides the user
with less detailed information about the more complex data entries
than the backward-compatible versions. It is possible to provide more
information, but this involves pulling apart the regular expressions
to test for subsets of matches. In the first one,
isNotEmpty( ), the regular expression pattern
looks for a string with one or more characters of any kind
(.+). To test for a number in isNumber(
), the pattern looks for a string that begins with
(^) zero or one minus signs
([-]?), followed by zero or more numerals
(\d*), zero or one decimals
(\.?), and zero or more numerals
(\d*) on the tail end ($). A
fixed-length string pattern in isLen16( ) looks
for word boundaries on both ends (\b) and any
characters (.) appearing 16 times
({16}). To ensure the user keeps to the
16-character length, limit the text-type
input element to a maximum length of 16.
The gnarled email pattern inside isEMailAddr( )
looks for a match that begins (^) with one or more
letters, numerals, underscores, or hyphens
([\w-]), followed by zero or more combinations of
a period, letter, numeral, underscore, or hyphen
((\.[\w-]+)*), followed by the
@ sign, followed by one or more computer or
domains (([\w-]+\.)+), followed by two to seven
upper- or lowercase letters for the top-level domain name
([a-zA-Z]{2,7}) on the tail end
($). The pattern does not match the use of
straight IP addresses for the portion after the @
sign, but the email message specification (Internet Engineering Task
Force RFC 822) frowns on such usage anyway.
In addition to individual validation routines, you sometimes need to
cascade them. For example, none of the functions that validate
numbers, fixed-length strings, or email addresses perform any
checking that assures the field has something in it. For example, if
the email address field is a required field in the form, you would
wire the onchange event handler for that
input element to pass the values first to the
isNotEmpty( ) function and then the
isEMailAddr( ) function—but in such a way
that if the first fails, the second one does not execute.
That's where the returned Boolean values of the
functions come into play:
<input type="text" size="30" name="eMail" id="eMail"
onchange="if (isNotEmpty(this)) {isEMailAddr(this)}" />
Not shown among the text field validation routines here
is one that validates a date entry. Validating date entries is tricky
business due to the wide range of date formats and sequences of
numbers used around the world. Except for intranet application where
everyone standardizes on a single date format, I recommend
implementing date input as three distinct fields (or
select elements) for entry of month, date, and
year. Use the onsubmit event handler to combine
the values into a single string for a hidden input element whose
value the server can pass directly to the database. You can also use
the pop-up calendar shown in Recipe 15.9 to help a user select a
date, leaving the formatting up to your scripts. Or, if all of your
users follow a fixed date format, you can try the date validation
techniques described in Recipe 2.12.
8.2.4 See Also
Recipe 8.3 for a batch validation structure that uses the functions
just described; Recipe 8.4 for automatically focusing and selecting a
text field that fails validation; Recipe 2.12 for date validation
ideas.
|