Handling forms II
Objectives
- use JavaScript to validate additional types of form elements
- create a generic and powerful form validator using JavaScript
- handle form errors in a sophisticated manner
Special note for HTML5
HTML5 has new form elements and some automatic validation. These notes cover
pre-HTML5 forms since there is still a lot of variation in what browsers
support as these notes are being written.
Complex validation and additional form elements
- This example demonstrates somewhat complex form validation
including checking for integer and floating-point numbers,
zip codes, and emails. It also demonstrates how to set
values on radio buttons and check boxes, and how to respond
to a radio button being clicked.
- To see if a radio button or checkbox is selected, you can use
their "checked" property.
- You can access a particular radio button or checkbox within a named
grouping by using an index since they are automatically placed into
an array (eg. female = frm1.gender[1].checked; )
- You can select or unselect a radio button or checkbox using the
element's "checked" property (eg. f.hobby[0].checked = true;).
- Please note that there is a JavaScript function named "isFinite"
which can tell you whether or not a string represents a valid
number. Using "isFinite" or a regular expression to test for a
valid number is probably a better approach.
- See form6.html:
<html>
<head>
<title>form6.html</title>
<script>
// sets the default gender radio buttons
// - in this case, to 'M'
// calls setHobby at end
function setGenderDefault() {
var f = document.frm1;
for (var i=0; i<f.gender.length; i++) {
if (f.gender[i].value == "M") {
f.gender[i].checked = true;
}
}
setHobby();
}
// This function sets a default hobby in checkboxes.
// The default hobby is different depending on the gender
// selected. Males get sports. Females get music. Sexist?
// Then change it to what you want. It is here merely to
// demonstrate how to set varying default values based on
// a different field.
function setHobby() {
var i;
var f = document.frm1;
for (i=0; i<f.hobby.length; i++)
f.hobby[i].checked = false;
for (i=0; i<f.gender.length; i++) {
if (f.gender[i].checked) {
if (f.gender[i].value == "M") {
f.hobby[0].checked = true;
} else {
f.hobby[1].checked = true;
}
}
}
}
// val is string being checked
// decAllowed is true if a decimal point is found
// in this case, + and - signs are NOT allowed
function isNum(val, decAllowed) {
var dec = false; // indicates whether decimal found
var i, c;
// required field
if (val == "") return false;
// check each character in val
for (i=0; i<val.length; i++) {
c = val.charAt(i);
// check to see if character is decimal point
if (c == ".") {
// if no decimal allowed or there was already a
// decimal point found in val, then val is invalid
if (!decAllowed) return false;
if (dec == true) return false;
// val valid, but set flag to indicate decimal point found
else dec = true;
} else {
// if character is not a decimal point, it must be a digit
if (c < "0" || c > "9") return false;
}
}
return true;
}
function isZip(val) {
// required field
if (val == "") return false;
// length must be either 5, 9, or 10
if (val.length == 5 || val.length == 9)
// lengths 5 and 9 must be numeric
return isNum(val, false);
// if length 10, check for zip plus 4 using dash
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == "-";
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate() {
var f = document.frm1;
var valid = true;
if (!isZip(f.zip.value)) {
alert("Invalid zipcode");
valid = false;
}
if (!isEmail(f.email.value)) {
alert("Invalid email");
valid = false;
}
return valid;
}
window.onload = function() {
var f = document.frm1;
f.onsubmit = validate;
f.gender[0].onclick = setHobby;
f.gender[1].onclick = setHobby;
f.gender[0].click();
f.onreset = setGenderDefault;
};
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<p>Gender: <input type="radio" name="gender" id="male" value="M" checked />Male
     
<input type="radio" name="gender" id="female" value="F" />Female</p>
<p>Interests:
<input type="checkbox" name="hobby"
value="sports" />Sports
   
<input type="checkbox" name="hobby"
value="music" />Music
   
<input type="checkbox" name="hobby"
value="cowTipping" />Cow Tipping</p>
<p>Zipcode: <input type="text" name="zip" id="zip"
size="10" /></p>
<p>Email: <input type="text" name="email" id="email"
size="40" /></p>
<input type="reset" />
     
<input type="submit" />
</form>
</body>
</html>
- A variation of the same page is at form6b.html:
<html>
<head>
<title>form6b.html</title>
<script>
window.onload = init;
function init() {
var f = document.forms[0];
setGenderDefault(f);
for (var i=0; i<f["gender"].length; i++) {
f["gender"][i].onclick = function() { setHobby(f); };
}
f.onsubmit = function() { return validate(f); };
f.onreset = function() { setGenderDefault(f); return false; };
}
// sets the default gender radio buttons
// - in this case, to 'M'
// calls setHobby at end
function setGenderDefault(f) {
for (var i=0; i<f["gender"].length; i++) {
if (f["gender"][i].value == "M") {
f["gender"][i].checked = true;
}
}
setHobby(f);
}
// This function sets a default hobby in checkboxes.
// The default hobby is different depending on the gender
// selected. Males get sports. Females get music. Sexist?
// Then change it to what you want. It is here merely to
// demonstrate how to set varying default values based on
// a different field.
function setHobby(f) {
var i;
for (i=0; i<f["hobby"].length; i++)
f["hobby"][i].checked = false;
for (i=0; i<f["gender"].length; i++) {
if (f["gender"][i].checked) {
if (f["gender"][i].value == "M") {
f["hobby"][0].checked = true;
} else {
f["hobby"][1].checked = true;
}
}
}
}
// val is string being checked
// decAllowed is true if a decimal point is found
// in this case, + and - signs are NOT allowed
function isNum(val, decAllowed) {
var dec = false; // indicates whether decimal found
var i, c;
// required field
if (val == "") return false;
// check each character in val
for (i=0; i<val.length; i++) {
c = val.charAt(i);
// check to see if character is decimal point
if (c == ".") {
// if no decimal allowed or there was already a
// decimal point found in val, then val is invalid
if (!decAllowed) return false;
if (dec == true) return false;
// val valid, but set flag to indicate decimal point found
else dec = true;
} else {
// if character is not a decimal point, it must be a digit
if (c < "0" || c > "9") return false;
}
}
return true;
}
function isZip(val) {
// required field
if (val == "") return false;
// length must be either 5, 9, or 10
if (val.length == 5 || val.length == 9)
// lengths 5 and 9 must be numeric
return isNum(val, false);
// if length 10, check for zip plus 4 using dash
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == "-";
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var valid = true;
if (!isZip(f["zip"].value)) {
alert("Invalid zipcode");
valid = false;
}
if (!isEmail(f["email"].value)) {
alert("Invalid email");
valid = false;
}
return valid;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<p>Gender: <input type="radio" name="gender" value="M" />Male
     
<input type="radio" name="gender" value="F" />Female</p>
<p>Interests:
<input type="checkbox" name="hobby" value="sports" />Sports
   
<input type="checkbox" name="hobby" value="music" />Music
   
<input type="checkbox" name="hobby" value="cowTipping" />Cow Tipping</p>
<p>Zipcode: <input type="text" name="zip" id="zip" size="10" /></p>
<p>Email: <input type="text" name="email" id="email" size="40" /></p>
<input type="reset" />      
<input type="submit" />
</form>
</body>
</html>
- For a more modern version see form6c.html:
<html>
<head>
<title>form6c.html</title>
<script>
window.onload = init;
function $(id) {
return document.getElementById(id);
}
function init() {
var f = $('frm1');
setGenderDefault(f);
for (var i=0; i<f["gender"].length; i++) {
f["gender"][i].onclick = function() { setHobby(f); };
}
f.onsubmit = function() { return validate(f); };
f.onreset = function() { setGenderDefault(f); return false; };
}
// sets the default gender radio buttons
// - in this case, to 'M'
// calls setHobby at end
function setGenderDefault(f) {
for (var i=0; i<f["gender"].length; i++) {
if (f["gender"][i].value == "M") {
f["gender"][i].checked = true;
}
}
setHobby(f);
}
// This function sets a default hobby in checkboxes.
// The default hobby is different depending on the gender
// selected. Males get sports. Females get music. Sexist?
// Then change it to what you want. It is here merely to
// demonstrate how to set varying default values based on
// a different field.
function setHobby(f) {
var i;
for (i=0; i<f["hobby"].length; i++)
f["hobby"][i].checked = false;
for (i=0; i<f["gender"].length; i++) {
if (f["gender"][i].checked) {
if (f["gender"][i].value == "M") {
f["hobby"][0].checked = true;
} else {
f["hobby"][1].checked = true;
}
}
}
}
// val is string being checked
// decAllowed is true if a decimal point is found
// in this case, + and - signs are NOT allowed
function isNum(val, decAllowed) {
var dec = false; // indicates whether decimal found
var i, c;
// required field
if (val == "") return false;
// check each character in val
for (i=0; i<val.length; i++) {
c = val.charAt(i);
// check to see if character is decimal point
if (c == ".") {
// if no decimal allowed or there was already a
// decimal point found in val, then val is invalid
if (!decAllowed) return false;
if (dec == true) return false;
// val valid, but set flag to indicate decimal point found
else dec = true;
} else {
// if character is not a decimal point, it must be a digit
if (c < "0" || c > "9") return false;
}
}
return true;
}
function isZip(val) {
// required field
if (val == "") return false;
// length must be either 5, 9, or 10
if (val.length == 5 || val.length == 9)
// lengths 5 and 9 must be numeric
return isNum(val, false);
// if length 10, check for zip plus 4 using dash
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == "-";
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var valid = true;
if (!isZip($('zip').value)) {
alert("Invalid zipcode");
valid = false;
}
if (!isEmail($('email').value)) {
alert("Invalid email");
valid = false;
}
return valid;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<p>Gender: <input type="radio" name="gender" value="M" />Male
     
<input type="radio" name="gender" value="F" />Female</p>
<p>Interests:
<input type="checkbox" name="hobby" value="sports" />Sports
   
<input type="checkbox" name="hobby" value="music" />Music
   
<input type="checkbox" name="hobby" value="cowTipping" />Cow Tipping</p>
<p>Zipcode: <input type="text" name="zip" id="zip" size="10" /></p>
<p>Email: <input type="text" name="email" id="email" size="40" /></p>
<input type="reset" />      
<input type="submit" />
</form>
</body>
</html>
Sophisticated error handling
- This page demonstrates a more sophisticated approach to form validation.
- Notice how form element properties are set when an attempt is made to submit
the form. These settings then control which fields are checked and what they
are checked for.
- Checking routines are included for email, zip, integers, floating-point values,
minimum and maximum numeric values, and blank fields.
- The generic validation routines would traditionally be included from
a separate file using a link in the head element. That would facilitate their
reuse by other pages on the site.
- The generic nature of this code makes it easy to add functions to do new
types of validation.
- The errors that are found are all concatenated and displayed. This
is different than the way validation programs often show you
only the first error message before quitting.
- see form7.html:
<html>
<head>
<title>form7.html</title>
<script>
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var i, e, msg = "";
for (i=0; i<f.length; i++) {
e = f.elements[i];
if (e.req) {
if (isBlank(e.value)) {
msg += "Missing " + e.name + " field\n";
continue;
}
}
if (e.valtest) {
var test = e.valtest;
if (test == "email") {
if (!isEmail(e.value)) {
msg += "Invalid email field\n";
continue;
}
} else if (test == "zip") {
if (!isZip(e.value)) {
msg += "Invalid zipcode field\n";
continue;
}
} else if (test == "integer") {
if (!isNum(e.value, false)) {
msg += e.name + " not valid integer field\n";
continue;
}
} else if (test == "float") {
if (!isNum(e.value, true)) {
msg += e.name + " not valid floating-point field\n";
continue;
}
}
}
if (e.min) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) < e.min) {
msg += e.name + " field below minimum value (" + e.min + ")\n";
continue;
}
}
if (e.max) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) > e.max) {
msg += e.name + " field above maximum value (" + e.max + ")\n";
continue;
}
}
}
if (msg != "") {
alert(msg);
return false;
}
return true;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1"
onsubmit="this.nm.req=true;
this.zip.valtest='zip';
this.email.valtest='email';
this.numfield.min=30;
this.numfield.max=100;
return validate(this);">
<p>Name: <input type="text" name="nm" id="nm" size="20" /></p>
<p>Zipcode: <input type="text" name="zip" id="zip" size="10" /></p>
<p>Email: <input type="text" name="email" id="email" size="40" /></p>
<p>Number: <input type="text" name="numfield" id="numfield" /></p>
<input type="reset" />     
<input type="submit" />
</form>
</body>
</html>
- A nicer version of the same page is at progs/form7b.html:
<html>
<head>
<title>form7b.html</title>
<script>
window.onload = function() {
var f = document.forms["frm1"];
f["nm"].req=true;
f["zip"].valtest='zip';
f["email"].valtest='email';
f["numfield"].min=30;
f["numfield"].max=100;
f.onsubmit=function() { return validate(f); };
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var i, e, msg = "";
for (i=0; i<f.length; i++) {
e = f.elements[i];
if (e.req) {
if (isBlank(e.value)) {
msg += "Missing " + e.name + " field\n";
continue;
}
}
if (e.valtest) {
var test = e.valtest;
if (test == "email") {
if (!isEmail(e.value)) {
msg += "Invalid email field\n";
continue;
}
} else if (test == "zip") {
if (!isZip(e.value)) {
msg += "Invalid zipcode field\n";
continue;
}
} else if (test == "integer") {
if (!isNum(e.value, false)) {
msg += e.name + " not valid integer field\n";
continue;
}
} else if (test == "float") {
if (!isNum(e.value, true)) {
msg += e.name + " not valid floating-point field\n";
continue;
}
}
}
if (e.min) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) < e.min) {
msg += e.name + " field below minimum value (" + e.min + ")\n";
continue;
}
}
if (e.max) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) > e.max) {
msg += e.name + " field above maximum value (" + e.max + ")\n";
continue;
}
}
}
if (msg != "") {
alert(msg);
return false;
}
return true;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="nm" id="nm" size="20" /></td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zip" id="zip" size="10" /></td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" /></td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="numfield" id="numfield" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>
- A more modern version of the same page is at progs/form7c.html:
<html>
<head>
<title>form7c.html</title>
<script>
function $(id) {
return document.getElementById(id);
}
window.onload = function() {
$('nm').req=true;
$('zip').valtest='zip';
$('email').valtest='email';
$('numfield').min=30;
$('numfield').max=100;
$('frm1').onsubmit=function() { return validate($('frm1')); };
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var i, e, msg = "";
for (i=0; i<f.length; i++) {
e = f.elements[i];
if (e.req) {
if (isBlank(e.value)) {
msg += "Missing " + e.name + " field\n";
continue;
}
}
if (e.valtest) {
var test = e.valtest;
if (test == "email") {
if (!isEmail(e.value)) {
msg += "Invalid email field\n";
continue;
}
} else if (test == "zip") {
if (!isZip(e.value)) {
msg += "Invalid zipcode field\n";
continue;
}
} else if (test == "integer") {
if (!isNum(e.value, false)) {
msg += e.name + " not valid integer field\n";
continue;
}
} else if (test == "float") {
if (!isNum(e.value, true)) {
msg += e.name + " not valid floating-point field\n";
continue;
}
}
}
if (e.min) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) < e.min) {
msg += e.name + " field below minimum value (" + e.min + ")\n";
continue;
}
}
if (e.max) {
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) > e.max) {
msg += e.name + " field above maximum value (" + e.max + ")\n";
continue;
}
}
}
if (msg != "") {
alert(msg);
return false;
}
return true;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="nm" id="nm" size="20" /></td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zip" id="zip" size="10" /></td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" /></td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="numfield" id="numfield" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>
Automating validation using class names for fields
- In this example, the validation is automated by just giving specific
class names to the fields you want validated. The validation routine
looks for all elements on the form with those class names and
picks the proper validation routine to run.
- The class names allowed in this script are:
- chkRequired: field required, may not be empty
- chkEmail: field required and must be valid email address
- chkZip: field required and must be valid zip or zip+4
- chkMin20: field required and must be at least 20; change the trailing
digits to change the minimum value
- chkMax70: field required and must be no more than 70; change the
trailing digits to change the maximum value
- chkFloat: field required and must be valid floating point number (+,- signs not allowed)
- chkInteger: field required and must be valid integer (+,- signs not allowed)
- Error messages are presented combined in an alert box when submission is attempted.
- The validation routines will simplify quite a bit once we learn how to
use regular expressions. Some regular expressions are already present on
this page.
- Please note that a more generic validation routine should probably
separate some of the "required" tests from the other tests. This
would allow the designer of the form to specify required only when needed, and
tests such as chkZip could return valid if the field is not
filled in.
- see form8.html:
<html>
<head>
<title>form8.html</title>
<script>
window.onload = function() {
var f = document.forms["frm1"];
f.onsubmit=function() { return validate(f); };
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var i, e, msg = "";
for (i=0; i<f.elements.length; i++) {
e = f.elements[i];
if (/(^| )chkRequired( |$)/.test(e.className)) {
if (isBlank(e.value)) {
msg += "Missing " + e.name + " field\n";
continue;
}
}
if (/(^| )chkEmail( |$)/.test(e.className)) {
if (!isEmail(e.value)) {
msg += "Invalid email field\n";
continue;
}
} else if (/(^| )chkZip( |$)/.test(e.className)) {
if (!isZip(e.value)) {
msg += "Invalid zipcode field\n";
continue;
}
} else if (/(^| )chkInteger( |$)/.test(e.className)) {
if (!isNum(e.value, false)) {
msg += e.name + " not valid integer field\n";
continue;
}
} else if (/(^| )chkFloat( |$)/.test(e.className)) {
if (!isNum(e.value, true)) {
msg += e.name + " not valid floating-point field\n";
continue;
}
}
if (/(^| )chkMin\d+( |$)/.test(e.className)) {
var eMinStr = e.className.match(/(^| )chkMin\d+( |$)/)[0];
var eMin = parseFloat(eMinStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) < eMin) {
msg += e.name + " field below minimum value (" + eMin + ")\n";
continue;
}
}
if (/(^| )chkMax\d+( |$)/.test(e.className)) {
var eMaxStr = e.className.match(/(^| )chkMax\d+( |$)/)[0];
var eMax = parseFloat(eMaxStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) > eMax) {
msg += e.name + " field above maximum value (" + eMax + ")\n";
continue;
}
}
}
if (msg != "") {
alert(msg);
return false;
}
return true;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="nm" id="nm" size="20" class="chkRequired" /></td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zip" id="zip" size="10" class="chkZip" /></td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" class="chkEmail" /></td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="numfield" id="numfield" class="chkMin30 chkMax100" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>
- A more modern version of the same page is at progs/form8b.html:
<html>
<head>
<title>form8b.html</title>
<script>
function $(id) {
return document.getElementById(id);
}
window.onload = function() {
$('frm1').onsubmit=function() { return validate($('frm1')); };
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function validate(f) {
var i, e, msg = "";
for (i=0; i<f.elements.length; i++) {
e = f.elements[i];
if (/(^| )chkRequired( |$)/.test(e.className)) {
if (isBlank(e.value)) {
msg += "Missing " + e.name + " field\n";
continue;
}
}
if (/(^| )chkEmail( |$)/.test(e.className)) {
if (!isEmail(e.value)) {
msg += "Invalid email field\n";
continue;
}
} else if (/(^| )chkZip( |$)/.test(e.className)) {
if (!isZip(e.value)) {
msg += "Invalid zipcode field\n";
continue;
}
} else if (/(^| )chkInteger( |$)/.test(e.className)) {
if (!isNum(e.value, false)) {
msg += e.name + " not valid integer field\n";
continue;
}
} else if (/(^| )chkFloat( |$)/.test(e.className)) {
if (!isNum(e.value, true)) {
msg += e.name + " not valid floating-point field\n";
continue;
}
}
if (/(^| )chkMin\d+( |$)/.test(e.className)) {
var eMinStr = e.className.match(/(^| )chkMin\d+( |$)/)[0];
var eMin = parseFloat(eMinStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) < eMin) {
msg += e.name + " field below minimum value (" + eMin + ")\n";
continue;
}
}
if (/(^| )chkMax\d+( |$)/.test(e.className)) {
var eMaxStr = e.className.match(/(^| )chkMax\d+( |$)/)[0];
var eMax = parseFloat(eMaxStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
msg += "Invalid " + e.name + " field\n";
continue;
} else if (parseFloat(e.value) > eMax) {
msg += e.name + " field above maximum value (" + eMax + ")\n";
continue;
}
}
}
if (msg != "") {
alert(msg);
return false;
}
return true;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="nm" id="nm" size="20" class="chkRequired" /></td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zip" id="zip" size="10" class="chkZip" /></td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" class="chkEmail" /></td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="numfield" id="numfield" class="chkMin30 chkMax100" /></td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>
Automating placement of error messages on form
- This page allows the fields to designate a location to place error messages.
The location is specified by adding a class name to each field to be validated.
The new class name must have the prefix "error_". The rest of the
class name must be the HTML id value of the element where the error message
is to be placed.
- The script also relies on two CSS selectors to be present, .hide and .error.
The .hide CSS selector must make the element invisible. The .error CSS
selector should be the styling you want for your error messages. The validation
routine will set the designated error message locations to one of those two
classes depending on the results of the validation.
- This should be pretty slick if it works.
- see form9.html:
<html>
<head>
<title>form9.html</title>
<style type="text/css">
.hide { visibility: hidden; }
.error {
visibility: visible;
font-weight: bold;
color: red;
}
</style>
<script>
window.onload = function() {
var f = document.forms["frm1"];
f.onsubmit=function() { return validate(f); };
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function showError(id, msg) {
var e = document.getElementById(id);
if (e == null) return;
e.innerHTML = '<b>' + msg + '<\/b>';
e.className = 'error';
}
function noError(id) {
var e = document.getElementById(id);
if (e == null) return;
e.innerHTML = '';
e.className = 'hide';
}
function validate(f) {
var i, e, msg = "", valid=true;
for (i=0; i<f.elements.length; i++) {
e = f.elements[i];
var errorId = "";
if (/(^| )error_\w+( |$)/.test(e.className)) {
errorId = e.className.match(/(^| )error_\w+( |$)/)[0];
errorId = errorId.match(/\w+/)[0].substr(6);
}
if (/(^| )chkRequired( |$)/.test(e.className)) {
if (isBlank(e.value)) {
if (errorId != "") showError(errorId, "Missing " + e.name + " field");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkEmail( |$)/.test(e.className)) {
if (!isEmail(e.value)) {
if (errorId != "") showError(errorId, "Invalid email field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkZip( |$)/.test(e.className)) {
if (!isZip(e.value)) {
if (errorId != "") showError(errorId, "Invalid zipcode field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkInteger( |$)/.test(e.className)) {
if (!isNum(e.value, false)) {
if (errorId != "") showError(errorId, e.name + " not valid integer field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkFloat( |$)/.test(e.className)) {
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, e.name + " not valid floating-point field");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkMin\d+( |$)/.test(e.className)) {
var eMinStr = e.className.match(/(^| )chkMin\d+( |$)/)[0];
var eMin = parseFloat(eMinStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, "Invalid " + e.name + " field");
valid = false;
continue;
} else if (parseFloat(e.value) < eMin) {
if (errorId != "") showError(errorId, e.name + " field below minimum value (" + eMin + ")");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkMax\d+( |$)/.test(e.className)) {
var eMaxStr = e.className.match(/(^| )chkMax\d+( |$)/)[0];
var eMax = parseFloat(eMaxStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, "Invalid " + e.name + " field");
valid = false;
continue;
} else if (parseFloat(e.value) > eMax) {
if (errorId != "") showError(errorId, e.name + " field above maximum value (" + eMax + ")");
valid = false;
continue;
} else {
noError(errorId);
}
}
}
return valid;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="name" id="name" size="20" class="chkRequired error_errNm" /></td>
<td id="errNm" class="hide">Name field required</td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zipcode" id="zipcode" size="10" class="chkZip error_errZip" /></td>
<td id="errZip" class="hide">Invalid zipcode</td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" class="chkEmail error_errEmail" /></td>
<td id="errEmail" class="hide">Invalid email</td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="Number" id="Number" class="chkMin30 chkMax100 error_errNum" /></td>
<td id="errNum" class="hide">Invalid number (must be 30-100)</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>
- for a more modern version see form9b.html:
<html>
<head>
<title>form9b.html</title>
<style>
.hide { visibility: hidden; }
.error {
visibility: visible;
font-weight: bold;
color: red;
}
</style>
<script>
function $(id) {
return document.getElementById(id);
}
window.onload = function() {
$('frm1').onsubmit=function() { return validate($('frm1')); };
// Following statement could use form's .onreset handler in HTML 5
$('reset').onclick=function() { clearErrors(); };
}
function clearErrors() {
var allTags = document.getElementsByTagName("*");
for (var tag=0; tag<allTags.length; tag++) {
var classes = allTags[tag].className;
var errorMessages = classes.match(/\berror_[^ ]+\b/g);
if (errorMessages == null) continue;
for (var errClass=0; errClass<errorMessages.length; errClass++) {
var errId = errorMessages[errClass].substr(6);
var errElement = $(errId);
if (errElement != null) {
errElement.innerHTML = "";
errElement.className = errElement.className.replace(/error ?/, "");;
}
}
}
}
function isBlank(val) {
if (val.length == 0) return true;
for (var i=0; i < val.length; i++) {
c = val.charAt(i);
if (c != ' ' && c != '\n' && c != '\t')
return false;
}
return true;
}
function isNum(val, decAllowed) {
var dec = false;
var i, c;
if (val == "") return false;
for (i=0; i<val.length; i++) {
c = val.charAt(i);
if (c == '.') {
if (!decAllowed) return false;
if (dec == true) return false;
else dec = true;
} else {
if (c < '0' || c > '9') return false;
}
}
return true;
}
function isZip(val) {
if (val == "") return false;
if (val.length == 5 || val.length == 9)
return isNum(val, false);
else if (val.length == 10) {
return isNum(val.substr(0,5), false) &&
isNum(val.substr(6,4), false) &&
val.charAt(5) == '-';
}
return false;
}
function isEmail(val) {
var i, p, p2;
var badChars = " /:;,";
// field must not be empty
if (val.length == 0) return false;
// check for illegal characters
for (i=0; i<badChars.length; i++) {
p = val.indexOf(badChars.charAt(i));
if (p != -1) return false;
}
// there must be at least one "@"
p = val.indexOf("@");
if (p == -1) return false;
// "@" may not be first character
if (p == 0) return false;
// there may be only one "@"
if (val.indexOf("@", p+1) != -1) return false;
// there must be at least one "." after the "@"
p2 = val.lastIndexOf(".");
if (p2 < p) return false;
// there must be at least two chars after the last period
if (p2 > val.length-3) return false;
// passed these validation tests
return true;
}
function showError(id, msg) {
var e = document.getElementById(id);
if (e == null) return;
e.innerHTML = '<b>' + msg + '<\/b>';
e.className = 'error';
}
function noError(id) {
var e = document.getElementById(id);
if (e == null) return;
e.innerHTML = '';
e.className = 'hide';
}
function validate(f) {
var i, e, msg = "", valid=true;
for (i=0; i<f.elements.length; i++) {
e = f.elements[i];
var errorId = "";
if (/(^| )error_\w+( |$)/.test(e.className)) {
errorId = e.className.match(/(^| )error_\w+( |$)/)[0];
errorId = errorId.match(/\w+/)[0].substr(6);
}
if (/(^| )chkRequired( |$)/.test(e.className)) {
if (isBlank(e.value)) {
if (errorId != "") showError(errorId, "Missing " + e.name + " field");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkEmail( |$)/.test(e.className)) {
if (!isEmail(e.value)) {
if (errorId != "") showError(errorId, "Invalid email field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkZip( |$)/.test(e.className)) {
if (!isZip(e.value)) {
if (errorId != "") showError(errorId, "Invalid zipcode field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkInteger( |$)/.test(e.className)) {
if (!isNum(e.value, false)) {
if (errorId != "") showError(errorId, e.name + " not valid integer field");
valid = false;
continue;
} else {
noError(errorId);
}
} else if (/(^| )chkFloat( |$)/.test(e.className)) {
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, e.name + " not valid floating-point field");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkMin\d+( |$)/.test(e.className)) {
var eMinStr = e.className.match(/(^| )chkMin\d+( |$)/)[0];
var eMin = parseFloat(eMinStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, "Invalid " + e.name + " field");
valid = false;
continue;
} else if (parseFloat(e.value) < eMin) {
if (errorId != "") showError(errorId, e.name + " field below minimum value (" + eMin + ")");
valid = false;
continue;
} else {
noError(errorId);
}
}
if (/(^| )chkMax\d+( |$)/.test(e.className)) {
var eMaxStr = e.className.match(/(^| )chkMax\d+( |$)/)[0];
var eMax = parseFloat(eMaxStr.match(/\d+/)[0]);
if (!isNum(e.value, true)) {
if (errorId != "") showError(errorId, "Invalid " + e.name + " field");
valid = false;
continue;
} else if (parseFloat(e.value) > eMax) {
if (errorId != "") showError(errorId, e.name + " field above maximum value (" + eMax + ")");
valid = false;
continue;
} else {
noError(errorId);
}
}
}
return valid;
}
</script>
</head>
<body>
<form action="receiver.html" method="get" name="frm1" id="frm1">
<!-- a table with one cell to shrink fieldset to form size -->
<table border="0"><tr><td>
<fieldset>
<legend>Test Form</legend>
<table border="0">
<tr>
<td><label for="nm">Name: </label></td>
<td><input type="text" name="name" id="name" size="20" class="chkRequired error_errNm" /></td>
<td id="errNm" class="hide">Name field required</td>
</tr>
<tr>
<td><label for="zip">Zipcode: </label></td>
<td><input type="text" name="zipcode" id="zipcode" size="10" class="chkZip error_errZip" /></td>
<td id="errZip" class="hide">Invalid zipcode</td>
</tr>
<tr>
<td><label for="email">Email: </label></td>
<td><input type="text" name="email" id="email" size="40" class="chkEmail error_errEmail" /></td>
<td id="errEmail" class="hide">Invalid email</td>
</tr>
<tr>
<td><label for="numfield">Number: </label></td>
<td><input type="text" name="Number" id="Number" class="chkMin30 chkMax100 error_errNum" /></td>
<td id="errNum" class="hide">Invalid number (must be 30-100)</td>
</tr>
<tr>
<td colspan="2" align="center">
<input type="reset" id="reset" />     
<input type="submit" />
</td>
</tr>
</table>
</fieldset>
</td></tr></table>
</form>
</body>
</html>