Validation

Provide a form validation library.

val.js (javascript, 3,362 bytes, form input validation, library, validate, zip codes, telephone numbers, credit card numbers, text, words, full names, email addresses, expiration dates, expires, URL, urls, check, error check)

Joseph K. Myers

Saturday, June 28, 2003

JavaScript Form Input Validation

Goal: I have a form which I wish to be filled in by a user. I do not wish to make the user be uncomfortable or annoyed by doing so, but I wish to validate the user's inputs by JavaScript. I want this to work easily on any form, with multiple types of form controls, and I wish the solution to be entirely customizable for special form values to be verified, while at the same time providing reasonable and carefree default checking on other fields. Overall, I want this to help the user to have an easier time filling in any form, and reduce the occurrence of mistakes in input which require the waste of money in contacting the user and requesting corrections for accidental errors.

Finally, I do not wish this to prohibit users from requests which may seem to be error only to an improperly written computer program. Not all errors can be detected by one program, nor can all correct possibilities be recognized, and so I wish the last approval of input to be up to me after the user has submitted the form--I do not wish the JavaScript itself to be responsible for blocking business which may be legitimate.


Long description.

With the exception of form upload fields, which must usually be validated by uploading itself (only their file name can be verified by JavaScript), all of these goals can be accomplished. The script will proceed in linear order through the form fields, with ways of handling each of the other types of inputs, notably, text, radio, checkbox, select, textarea.

Some of these kinds of inputs can possess arrays of input, rather than just one field. For example, in a form with two text fields named "hi", the script object named form.hi is not either of the text fields themselves. It is an object containing both of them. However, (supposing that the form only contains these two text inputs), the object named form.elements[0] is actually the first of the two input fields, and form.elements[1] is the second.

On some kinds of form fields, such as select menus, checkboxes, and radio buttons, the values are not checked, but the choices are checked. This is done by a detection in the validation logic. An ordinary function validates a value which is text. A validation function for a radio, checkbox, or select element will actually validate that element compared to other elements or other requirements--there is no sense in checking its values because those kinds of have predefined values.

This also means that a general-purpose JavaScript can't validate these fields completely by itself. An example would be buying a car in three packages, each with a number of options, but none of the more expensive options can be included with a car that is outfitted with a less expensive package. The validation function for the checkboxes might be able to do something like check the value of a checkbox to see if it was an option included in the car package that the user had selected. Then the validation script itself can let you take a shortcut, because once you have written such a function, all of those checkboxes can be validated with it.

Essentially, there is a JavaScript function "valid()" which is to check each element of the form. The form author creates a JavaScript list of field names to be checked in the form--if a field is listed, then it is checked.

The simplest sort of validation is "text." It is true if at least something in addition to whitespace (just several spaces would not count as text) is present within a field. Other verification functions are presented later on.

Another feature of the validation script may be of interest to you. The "filter" function offers the capability to clean up the results of the user's inputs, after they have been verified. It is easy to use, as shown in the examples. However, it is not to be used on elements besides text inputs and text area field.

As a cautionary note, here are some opinions written before designing some of the features of the form validation script. It turns out that validation may be bad if it prevents a user from the final decision before doing something.

Default values in fields that do not accept typed input are trusted because they were specified in the HTML, and in order to alter them one would have to alter the web page; of course, that is possible to do, but at the same time, one could disable the error checking function entirely. This points out an important fact: error checking is for user friendliness only--it is not to guarantee the validity of information submitted in any situation.

Some scripts are extremely irritating to the user, and these, I think, have missed the point entirely. They do not gain the assurance of valid data, for the reasons I have mentioned, and they do not gain the benefit of user friendliness--and I do not see any purpose in them. I wish above all to avoid being like this, demanding and nasty, and assuming that the script's validation techniques are valid, instead of a user who tries a second or third time to submit their information.

The real fact of this is that (1) a user could still submit all of their information in a form that could not be validated ever by anything except a human--writing your address on a single line could be an example--, (2) there could be an actual reason for this being the case--the HTML failed to load or a script failed to work, and so there was only one form field to fill in (and there could be lawfully justified intent: your brother has no other way to contact you; does he have a right to submit your form? According to a validation script it would probably be falsified information, but in such a case the ordinary assertion would itself be false.)--, and (3) a script does not do anything to a troublemaker, because why would they use the script?--and they of all people have a choice to avoid it which no webmaster or website can or may control.

The goal of the internet is not to force anything--think of who the visitor is--a person who came to your website, by being allowed to by the internet. Surely no one would try to control the entire internet population, nor assume they should all be visiting their web page, yet that is the way irritating scripts are--forgetting that that person owns themself and is allowed to leave--and the script cannot keep them from leaving.

My final decision on the subject was similar:

Basically, if they choose to do so, they must be allowed to submit the form.

Additional validation "stamp functions" (besides text) will be with the script for text validation of credit card numbers (any type), a full name (at least two words), zip code, a phone number (with area code, optional prefix numbers), a web address (URL), and an email address.

My goal in design was to create (or code in) at least the validation functions for those general kinds of input.

I succeeded; in fact, I added the expires function for expiration dates.

The code will try not to change any typed in values, except if filtering is set, and even then, only if the value was validated (to avoid clearing a field which simply was not quite finished being filled in). Any reformatting that needs to be done before validation (like taking out spaces in a credit card number) will not be shown in the text input, because it might make it harder to correct a mistake.

err = 0;
errc = ['OK'];
errc[1] = 'Please verify the zip code (5 or 9 digits).';
errc[2] = 'Please enter the telephone number, including area code.';
errc[3] = 'Please check the expiration date: four digits, '
+ 'e.g., 0709 is the correct date for July, 2009.';
errc[4] = 'This card is not working; can you check the card number again?';
errc[5] = 'Please do not leave this field blank.';
errc[6] = 'Please fill in the email address.';
errc[7] = 'Please check the expiration date: apparently it is expired.';
errc[8] = 'Please check the expiration date: it is not valid.';
errc[9] = 'Please complete the entry.';
errc[10] = 'Please enter a numerical value (may include an exponent e+N).';
errc[11] = 'Please enter a complete URL.';


function nwts(s) { // no white space
return s.replace(/\s+/g, '');
}
function trimwts(s) { // trim white space
return s.replace(/^\s+|\s+$/g, '');
}

function number(s) { // any number, including exponent
s = nwts(s)-0;
err = isNaN(s) ? 10 : 0;
return [err ? 0 : s];
}

function zip(s) { // zip code
var a = nwts(s).match(/\d{5}(-?\d{4}){0,1}/g);
err = a != null && a.length ? 0 : 1;
return a;
}

function tel(s) { // telephone, with area code + opt prefixes
var a = s.replace(/\D+/g, '-');
a = a.match(/(\d+-?)*(\d{3}-?){2}\d{4}/g);
err = a != null && a.length ? 0 : 2;
return a;
}

/* this code is copied from
http://www.codelib.net/home/jkm/checksum.js */
function checksum(s) { // thanks to daniel_amor@hp.com for AMEX specs
var p=0, e=8, t=0, c=[], r=0, l=0, i;
if (s.length != 16) {
t = 1;
e = s.length == 13 && 6 || s.length == 15 && 7;
}
for (i=p; i<e; i++)
r += (c[i] = s.charAt(i*2+t) * 2) > 9
? Math.floor(c[i]/10 + c[i]%10)
: c[i];
for (i=p; i<e+t; i++) l += s.charAt(i*2+1-t)-0;
l += r;
return e && l%10 == 0;
}

function cardno(s) {
s = s.replace(/\D+/g, '');
err = checksum(s) ? 0 : 4;
return [s];
}

function text(s) {
s = trimwts(s);
err = s.length ? 0 : 5;
return [s];
}

function words(s) {
s = trimwts(s);
err = /\s/.test(s) ? 0 : 9;
return [s];
}

function email(s) {
a = s.match(/\S+@([-\w]+\.)+\w+/g);
err = a != null && a.length ? 0 : 6;
return a;
}

function url(s) {
a = s.match(/\w{2,}:\/{2}([-\w]+\.)+\w+\S*/g);
err = a != null && a.length ? 0 : 11;
return a;
}

function expires(s) {
s = nwts(s);
var m = new Date()
var m_year = m.getFullYear()%100, m_month = m.getMonth();
if (s.length != 4 || isNaN(s))
err = 3;
else {
s_month = s.substring(0,2)-0;
s_year = s.substring(2, 4)-0;
if (m_year > 70 && s_year < 30) s_year += 100;
/* "Let your great grandson worry about that." */
if (s_year < m_year || s_year > m_year + 6) err = 8;
else if (s_year == m_year && !(s_month > m_month)) err = 7;
else err = 0;
}
return [s];
}

function valid(element, check) {
if (element.type == 'text' || element.type == 'textarea') {
return check(element.value);
} else return check(element);
}

present_element = null, present_error = null;
function validate(form, list) {
for (i=0; i<form.elements.length; i++) {
var element = form.elements[i];
var n = element.name, out;
if (list[n] && list[n].verify) {
out = valid(element, list[n].verify);
if (err && (list[n].force || list[n].err != err)) {
list[n].err = err;
present_error = err;
present_element = element;
alert(list[n].message || errc[err]);
element.focus();
return false;
} else if (!err && list[n].filter)
element.value = out.join(', ');
}
}
return true;
}

Another note:

The general policy is that we will never ask for the same field to be corrected twice in a row. If the same field is found to be in error again (with the same error code), the validation script must have already showed them the error message, and they must have already clicked "OK." Most of the time there is little point in repeating this silly song and dance. If the user thinks it is correct (even if it is not), we'll still let them submit the form--although you can overrule this behavior when you program using the validation script in your own forms: see the force option shown in one of the examples.

The rule is to show the user as few alert messages as possible, so that when one does appear, they will pay attention to it as something that is helpful.

It helps to keep somewhat of a perspective on the limited capabilities of JavaScript: there are many situations where the validation of JavaScript is not enough--such as a credit card number being suspended. So the main goal is to use the script to help people from having to say, "Oops! Now I have to spend half an hour trying to call the company and correct my order." The goal isn't to claim that only perfectly valid information will be given by all users--that is impossible.

If a "bad" user does try to submit information, it probably isn't going to hurt you, so long as it is not stolen from another person's identity (and if it were, JavaScript could not tell!). However, it will hurt you if for any reason you block a "good" user from doing business with you. The bottom line: trust the user more than you trust JavaScript. You are trying to keep people from forgetting things, not trying to say, "You are wrong," if they have already taken a second look at something that may have been wrong.

(For a note of philosophy. If they don't even know it, you can't get it from them.)

An example form

Step 1: Choose your car colors.

When you validate a set of fields, you must define their names, etc.

errc[31] = 'Please enter your two-letter state abbreviation.';
billing_address_check = {
name:{ verify: text, filter: true, message: 'Please enter your first name.' },
surname: { filter: 1,
verify: text, message: 'Please enter your last name.', filter:1 },
address: { filter: 1,
verify: words, message: 'Please enter your address.' },
city: { filter: 1, verify: text, message: 'Please enter your city.' },
state: { filter: 1, verify: function(s) {
 var a = nwts(s).toUpperCase().match(/^[a-z]{2}$/ig);
 err = a != null && a.length ? 0 : 31;
 return a;
} },
zip: { verify: zip, filter: 1, force: 1 },
phone: { verify: tel, filter: 1 },
email: { verify: email, filter: 1 }
};

The error relationships are best organized by the use of error codes. The script relies on them to know whether the same or a different error has occurred. The code numbers up to 30 are reserved. To add a new one, you do this:

errc[31] = 'Please enter your two-letter state abbreviation.';

etc.

When your custom error function sets an error code of 31, then this message will be given (all error messages may be replaced by an individual message appropriate for the field--but please note that in order for one field to report more than one kind of error, the error message from the error code must be used). The error code of 0 always indicates "no error" or "OK."

Also note that it is a good idea to supply your own error messages, appropriate for the style of your form, whether by using error codes or a constant error message for a field.

Step 2: Fill in your billing address.
Name, first and last
Address
City, State (two-letter abbreviation), and Zip
,
Phone (area code and number) and Email

On the first validation set, the additional "filter" setting was used. This usually will clean up things like extra spaces, and show the exact properly entered values, and it helps to show how the validation functions are actually working. The filter will not disturb the value of a field until it has actually passed validation--this prevents a field from being deleted in case someone has not finished filling it in.

In the second set this is not done, and it is probably better real-life practice to follow this example.

Note that there is an additional setting, which is not recommended to use, as it forces the user to be unable to submit the form unless the validation is passed, as long as they have JavaScript enabled. This is exactly like filter, so force: 1 within a validation set will never allow that field to be omitted.

Also note that these validation steps are separated mostly for the example of changing parameters--in real-life one would probably have created the entire validation process together. Or, one may find it easier and clearer to have each step of a form validated individually, such as is done here. This still allows the entire form to be validated together in the end (before submission): one should just do something like this:

<form onsubmit="return
validate(this, billing_address_check)
&amp;&amp; validate(this, billing_address_check)
&amp;&amp; validate(this, 
 this.other_shipping.checked
  ? shipping_address_check
  : {})
&amp;&amp; validate(this,
 this.mail_order.checked
  ? {}
  : billing_info_check)  
)

">

And of course, there may be a little bit of logic to the validation. Checking a blank set like {} instead of one of the real validation sets will allow us to skip one of the steps, for example, if they have chosen to place an order by mail.

Here is the next set of validations.

shipping_address_check = {
name2: { verify: text, message: 'Please enter the shipping first name.' },
surname2: { verify: text, message: 'Please enter the shipping last name.' },
address2: { verify: words, message: 'Please enter the shipping address.' },
city2: { verify: text, message: 'Please enter the shipping city.' },
state2: { verify: billing_address_check.state.verify,
message: 'Please enter the two-letter shipping state abbreviation.' },
zip2: { verify: zip }
};
Step 3: Fill in your shipping address (only if different):

Check the box if shipping to a different address.

Name, first and last
Address
City, State (two-letter abbreviation), and Zip
,

Validating other types of fields just requires writing a slightly different type of code. The validation function validates the whole element (it may need to evaluate several items), not simply a text string. The kind of field doesn't matter--you may even do some sort of check on a file upload field. Of course, it doesn't make much sense validating a hidden field--the user can't even change it.

Also, do not use any filter setting on this sort of validation. If there is some change to be made, one has access to the whole element, so it gives more freedom.

errc[33] = 'You may not include this option '
+ 'unless you choose a more expensive car package.';
function uncheckfeature(checkbox) {
var pkg = checkbox.form['package'].value,
boxpkg = checkbox.value.substring(0, 1);
if (checkbox.checked && boxpkg > pkg) {
err = 33;
}
else err = 0;
}

package_check = {
options: { verify: uncheckfeature, force: 1 }
};
Choose your equipment package and package options.

Yellow featheR window shadEs Tinted glaSs NevermInd waLlpAper how's MATchboxes bananAs gorillas outlaw ashtraYs digital Key EnTry

wow socks rubber plus 2 tires ne plus ultra! brand shelf gear sour coated windshield glass melody box antique record player

Lemon-flavored headphones Chocolate soapbox Sand dune hardware Green vitamin lids Mousy chewy seatbelt buckles Gum-proof carpet


/* this code is copied from
http://www.codelib.net/home/jkm/checksum.html */
function cardtypeOK(x) { // x is the number as text, like "123423..."
var a='456', i;
for (i=0; i<a.length; i++) if (x.charAt(0) == a.charAt(i)) break;
if (i == a.length) return false;
return true;
}
/*
where '456' are the beginning digits of credit
cards your business can accept (in this case visa,
mc, and discover).
*/

errc[32] = 'Your card is not one of the card types we can accept. '
+ 'Please use a VISA, MasterCard, or Discover card.';

billing_info_check = {
card: { verify: function(s) {
 s = cardno(s)
 if (!err && !cardtypeOK(s[0]))
  err = 32;
  return [s];
} },
cardname: { verify: words,
message: 'Please enter the full name as it appears on the card.' },
expiry: { verify: expires }
}
Step 4: Enter billing information:

We accept VISA, MC, and DISCOVER.

Card number
Name on card
Expiration date (four digits, e.g., 0709 for July, 2009)

Check the box if paying by check.

If you prefer to pay by check, fill in and print out this order form, and mail in your order to this address:

Consorting with Widgets, Inc.
71 Troublesome Zone
Polish Pie, AA, 12347

Validation functions

Just as "intelligence" without purpose is meaningless, the script to validate forms alone would be hardly formed with no definitions to validate: an icon placeless, a diamond with no ring.

The short and simple requirements of a validation function to be written are that it has to set an error code if there is an error, and it has to return an array of valid values (possibly with prettifications and reformatting) if there is no error.

The select superiority of the form validation script is not necessarily the validation--to an experienced programmer, anyone could write that. Without the included library of special validation add-ins, however, the validation and user alert messages, etc., would be ridiculous, like a computer without programs.

The purpose and value of the validation script is easy to recognize when wrapped around this library of special recognition techniques for many types of data: email addresses, expiration dates, credit card numbers. Each of them came along on its own, before this validation script was ever written, with its own purpose. Put together they make the validation script be something which could not be any other way. It is good to know that with these tested functions the validation script is production quality.

Here is their list. Note that the actual return values of all of them are arrays: it is possible for some, but not all of them, to return more than one value in the array, such as with email() for email addresses. Also note that the return value may not be an array if there has been an error code set.

zip
validation macro to define for a field for the zip values, either five digits or nine digits; as with other macros, it recognizes several variants, including spaces or no spaces, and with the case of a zip code, an optional hyphen. It is possible for multiple zip codes to be verified.
tel
telephone numbers, including long distance and country codes, and all kinds of separators.
cardno
checks any common type of credit card number.
text
checks that text (this does not mean alphabetical characters only) is present.
words
checks that at least two words of text are present.
email
validates all kinds of email addresses. It is possible for multiple email addresses to be validated.
expires
checks a four-digit expiration date, that it has not expired, and that it is not an invalid amount into the future (using the computer's own clock, which is occasionally mis-set, resulting in errors, but this is OK, since the customers will be allowed to judge for themselves after receiving one warning)
url
matches the wide range of possible URL values. Of course, it is possible that a URL may point to a non-existent location. It may return multiple values.

How to use them:

The zip macro, and all of these macros, are used in the same fashion. Some of the form fields, as many as you like, you place into a validation variable, like this:

check_form_example = {
};

Inside of the variable (which is an "object literal") you place the validation macro with the name of the appropriate field, like this:

check_form_example = {
zipcode: { verify: zip }
};

Or, to show the inner instruction all by itself:

zipcode: { verify: zip }

This would correspond to a field in your form something like this:

<input type="text" name="zipcode" />

To specify a custom error message, if the zip is invalid:

zipcode: { verify: zip,
message: 'Enter the zip code of your new home.' }

Note:

This message will always be shown, even if there are different error codes. For this reason you may sometimes not wish to specify a custom error message directly, but instead may wish to add your own error codes with new error code messages.

Also, you may feel comfortable overriding the default error message texts, which are within the reserved error code numbers 1-30.

In order to validate your form by this set of requirements, you will execute the function validate([form], check_form_example), which performs all the validation steps as you have specified, in a neat, orderly fashion. If the form fields within this variable, check_form_example, are indeed all validated, the function returns a value of true. This allows it to be in the submission pre-check of a form directly:

<form onsubmit="return validate(this, check_form_example)">

In this context, "this" is the correct code which refers to the actual form ("this" refers to the element in which an event hanlder is placed; the element in which the event handler is placed is the form itself).

As you may know already, JavaScript defines that if the onsubmit event handler function returns a value of false, the form will not be submitted. So if the validate() function has returned false, then the form is not submitted, and the user is able to correct the error of which they were alerted. If the validate function returns true, the form is submitted as normal.

Technical specifications

Speak of the form as the form submitted to the validate() function, and the check as the checking variable submitted to the validate() function.

Every element of the form with a name contained in check will be submitted to the function contained in check[name].verify, if such a function is present. The argument will be the value of the element if the element is text or textarea, otherwise the element object itself.

If an error code is set by this function, and if one of the following is true; the value of check[name].force is true, or the field has an an error that is different from an error it may have had immediately prior to this invocation of validation; then present_element is set to this element, check[name].err and present_error are set to the error number, and any error message is alerted, the element is focused, and false is returned from validate().

After the end of the form, true is returned from validate, but the end is not reached unless no errors have occurred.

Changes

3-18-04. Greg Dietsche observed that the validation code did not do a very good job of evaluating optional fields.

"When validating against a list such as this one which has two optional fields, occasionally a loop can ensue if neither of the optional fields (in this case, email and subject) are filled in by the user...

"To solve that problem, I made the following changes to the validation script. Basically the idea is that if the user has been shown a prompt for an *optional* field once, then they will never see the prompt again."

This was a nice suggestion, so it was taken and added to, as is usually the case with good ideas. Now the error state is maintained for each field, and optional fields bring up an alert message if they have a possible error, and if the error number is different from the error that may have been "detected" before.

Essentially, this lets a user leave the value of the field as they wish, if they want to ignore the error message.

Note that "optionally" checking fields in a form has a somewhat undefined meaning--"You don't have to fill it in, and if you do, it doesn't have to be right, but I'll check it and tell you any time that a different error happens in comparison to the possible way that the author thought this field might be filled in."

Contact information: Joseph Myers, cookies @ users.sourceforge.net