/*	see validate_test.js for an example	SETUP FOR FORM VALIDATION		1. include this script in your page	2. add onsubmit="return validate('formname',validate_settings)" to every form you wish to validate	3. create a validate_settings variable in the page to specify which things to check for	AVAILABLE SUBFUNCTIONS		not_blank -- make sure a field has something in it, or that a menu is set (the value of the "unset" option of menus should be -1 for this to work)	not_blank_menu -- make sure one element of a popup menu is selected (preferred over not_blank for menus)	not_blank_radio -- make sure one element of a radio button is selected	not_blank_checkbox -- make sure one element of a checkbox is selected	not_zero -- for numeric values, make sure the value is not zero	no_letters -- require that a field contain no letters (punctuation is okay)	no_numbers -- require that a field contain no numbers (punctuation is okay)	no_punctuation -- require that a field contain no punctuation	mixed_case -- don't allow all uppercase or all lowercase	match -- see if the field's value matches another field's value			 doesn't support menus, radio buttons, or checkboxes			 using the subfunction match_password_confirm checks to see if the field has the same value as the password_confirm field	allowed -- specify which characters are allowed in the field			   allowed_1234. allows only the numbers 1, 2, 3, 4 and a dot			   (actually, Netscape doesn't like slashes, even when escaped -- can we work around this in a future version?)	required -- specify which characters are required in the field			   required_a\n requires a letter a or a line break			   (actually, Netscape doesn't like slashes, even when escaped -- can we work around this in a future version?)	length -- check for a length range or list of possible lengths (counts letters and numbers only)			  also counts the length of a checkbox array			  length_6 requires 6 characters, length_1-12 requires between 1 and 12 characters, length_1|3|5 requires 1, 3, or 5 characters	email_characters -- check for various requirements of a valid email address	credit_card -- check for the proper lengths and required characters of each type of credit card (ignore punctuation)		      	   this assumes a field named card_type is also present on the form	taxexempt_format -- check if the value has a valid tax exempt ID number format (Exxxx-xxxx-xx)	SSN_format -- check if the value has a valid social security number format (xxx-xx-xxxx)			REGULAR EXPRESSION SYMBOLS USED IN THE SUBFUNCTIONS	\w matches alphanumeric, including "_".	\W matches non-alphanumeric (i.e., punctuation) 	\b matches word boundaries	\B matches non-boundaries 	\s matches whitespace	\S matches non-whitespace 	\d matches numeric	\D matches non-numeric 			VERSION HISTORY		01/19/02 - Initial development	10/21/02 - Added not_blank_menu, not_blank_radio, and not_blank_checkbox subfunctions; made card type switch not case-sensitive; added a setting to allow card_type to be a popup menu or a radio button	11/11/02 - Fixed get_checkbox_value and get_menu_value, added get_menu_text, tweaked focus on not_blank_menu to avoid errors	06/20/03 - Tweaked checkbox functions to support [] in element names; added match subfunction to compare two fields	07/22/03 - Added array_length check to get_checkbox_value, get_radio_value, not_blank_checkbox, and not_blank_radio to avoid an error when those arrays only contain one element	10/22/03 - Tweaked credit_card to avoid a duplicate missing card type message, fixed a typo in a comment	11/07/03 - Tweaked radio functions to support [] in element names	05/19/04 - Added support for a default blank_value for text fields; tweaked alert message to be more user-friendly	06/08/04 - Added support for customizing the alert message by providing an alternate initial sentence and/or skipping the field details.	06/08/04 - Added required subfunction, removed need to escape regex characters in allowed values.	08/05/04 - Tweaked previous change for compatibility with radio buttons and checkboxes	08/17/04 - Added show_full_report option to show just the first error message; tweaked not_blank_menu to correct problem introduced in previous version	10/14/04 - Changed lengthx function to count checkbox arrays	06/10/05 - Fixed match function to work when one field is blank; tweaked spacing and added some missing semicolons to line endings	06/17/05 - Changed some_caps to mixed_case so that all-uppercase entries as well as all-lowercase entries are blocked	01/24/06 - Added a stub function to pass some_caps calls to mixed_case for backwards compatibility	to do: make length function count items in a multiple-select menu	*/// offer a few site-wide settings; developers can also set these on a page-by-page basis in their scripts	var card_type_style = "menu"; // possible values are menu and radio	var blank_value = ""; // allows an initial value that's also considered blank, like "(one per line)"	var include_field_details = 1; // includes details about which fields have problems in the alert message	var show_full_report = 1; // includes all error messages; set to 0 to show just the first message 	var report_intro = "We're sorry, the information you entered is incomplete. Please correct the following:"; // the first sentence of the alert message	// parse the validate settings and trigger all the other functions	function validate(form_name, validate_settings) {		validate_report = "";		field_name_focus = "";				fields_count = validate_settings.length / 3;		for (i=0; i<fields_count; i++) {			field_name = validate_settings[i*3];			field_label = validate_settings[(i*3)+1];			field_settings = validate_settings[(i*3)+2];						if (field_settings.indexOf("not_blank_menu") != -1) {				field_value = get_menu_value(form_name, field_name);			} else if (field_settings.indexOf("not_blank_radio") != -1) {				field_value = get_radio_value(form_name, field_name);			} else if (field_settings.indexOf("not_blank_checkbox") != -1) {				field_value = get_checkbox_value(form_name, field_name);			} else if (field_settings.indexOf("length_") != -1) {				eval("field = document." + form_name + "." + field_name);				is_array = (typeof(field.length) == "undefined") ? 0 : 1 ;  // determine if this field is an array (checkbox group)				if (is_array) {					field_value = get_checkbox_value(form_name, field_name);				} else {					eval("field_value = document." + form_name + "." + field_name + ".value");				}			} else {				eval("field_value = document." + form_name + "." + field_name + ".value");			}						field_settings = field_settings.replace(/\\/g,"");																					field_settings = field_settings.replace(/,\s+/g,",");			field_settings = field_settings.split(",");			settings_count = field_settings.length;									for (j=0; j<settings_count; j++) {				function_name = field_settings[j];								// don't bother checking if the field is blank (unless we're checking not_blank!)				if ((function_name.indexOf("not_blank") != -1)||(function_name.indexOf("length") != -1)||(function_name.indexOf("match") != -1)||((field_value.replace(/\s/g,"") != "")&&(field_value.replace(/\s/g,"") != "-1"))) {					if (function_name.indexOf("length_") == 0) {						// do some special parsing for the length function						length_settings = function_name.substr(7,function_name.length);						lengthx(field_value, is_array, length_settings);					} else if (function_name.indexOf("allowed_") == 0) {						// do some special parsing for the allowed function						allowed_characters = function_name.substr(8,function_name.length);						allowed(field_value, allowed_characters);					} else if (function_name.indexOf("required_") == 0) {						// do some special parsing for the required function						required_characters = function_name.substr(9,function_name.length);						required(field_value, required_characters);					} else if (function_name.indexOf("match_") == 0) {						// do some special parsing for the match function						target_field = function_name.substr(6,function_name.length);						match(form_name, field_value, target_field, validate_settings);					} else if (function_name.indexOf("credit_card") == 0) {						// send the card_type value to the credit_card function						//eval("card_type = document."+form_name+".card_type.options[document."+form_name+".card_type.selectedIndex].text");						if (card_type_style == "menu") {							card_type = get_menu_text(form_name, "card_type");						} else {							// for this to return user-friendly results, you need to set your card IDs to words rather than numbers;							// this isn't a problem with menus because there you can have an ID (option.value) -and- a human-readable name (option.text)							card_type = get_radio_value(form_name, "card_type");						}						credit_card(field_value, card_type);					} else {						eval(function_name+"('"+eval_escape(field_value)+"')");					}								}			}		}				if (validate_report == "") {			return true;		} else {			if (include_field_details) {				divider = (report_intro && validate_report) ? "\n\n" : "" ;				if (!show_full_report) {					validate_report = validate_report.substr(0,validate_report.indexOf("\n"));				}				validate_report = report_intro + divider + validate_report;				alert(validate_report);			} else {				alert(report_intro);			}			if (field_name_focus) { eval("document."+form_name+"."+field_name_focus+".focus()"); }			return false;		}	}	// make sure a field has something in it, or that a menu is set (the value of the "unset" option of menus should be -1 for this to work)	function not_blank(field_value) {		if ((field_value.replace(/\s/g,"") == "")||(field_value == blank_value)||(field_value == "-1")) { // we could take out the -1 but for backwards compatibility with popup menus			validate_report += field_label + " is blank\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// make sure one element of a popup menu is selected	function not_blank_menu(field_value) {		if ((field_value == "")||(field_value == "-1")) {			validate_report += field_label + " is not selected\n";			if (! field_name_focus) {				field_name_focus = "elements['" + field_name + "']";			}		}	}// make sure one element of a radio button is selected	function not_blank_radio(field_value) {		if (field_value == "") {			validate_report += field_label + " is not selected\n";			if (! field_name_focus) {				if (array_length > 0) {					field_name_focus = "elements['" + field_name + "']" + "[0]";				} else {					field_name_focus = "elements['" + field_name + "']";				}			}		}	}// make sure one element of a checkbox is selected	function not_blank_checkbox(field_value) {		field_value_array = field_value.split(",");		this_length = field_value_array.length;				if (field_value == "") {			validate_report += field_label + " is not selected\n";			if (! field_name_focus) {				if (array_length > 0) {					field_name_focus = "elements['" + field_name + "']" + "[0]";				} else {					field_name_focus = "elements['" + field_name + "']";				}			}		}	}	// for numeric values, make sure the value is not zero		function not_zero(field_value) {		field_value = parseFloat(field_value);		if (field_value == 0) {			validate_report += field_label + " is blank\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// require that a field contain no letters (punctuation is okay)	function no_letters(field_value) {		field_value_compare = field_value.replace(/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/g,""); // remove all letters		if (field_value.length != field_value_compare.length) {			validate_report += field_label + " should only contain numbers\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// require that a field contain no numbers (punctuation is okay)	function no_numbers(field_value) {		field_value_compare = field_value.replace(/\d/g,""); // remove all numbers		if (field_value.length != field_value_compare.length) {			validate_report += field_label + " should not contain numbers\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// require that a field contain no punctuation	function no_punctuation(field_value) {		field_value_compare = field_value.replace(/\W/g,""); // remove all punctuation, except underscores		field_value_compare = field_value.replace(/_/g,""); // remove underscores		if (field_value.length != field_value_compare.length) {			validate_report += field_label + " should not contain numbers\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// encourage proper capitalization	// provide backwards compatibility with the old name for this subfunction	function some_caps(field_value) {		mixed_case(field_value);	}		function mixed_case(field_value) {		field_value_compare = field_value.replace(/[abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ]/g,"");		if (field_value_compare.length < field_value.length) { // don't bother checking if there aren't any letters			field_value_lower = field_value.toLowerCase(); // change to all lowercase			field_value_upper = field_value.toUpperCase(); // change to all uppercase			if ((field_value == field_value_lower)||(field_value == field_value_upper)) {				validate_report += field_label + " should use normal capitalization\n";				if (! field_name_focus) { field_name_focus = field_name; }			}		}	}// see if the field's value matches another field's value// doesn't support menus, radio buttons, or checkboxes	function match(form_name,field_value,target_field_name,validate_settings) {		eval("target_field_value = document."+form_name+"."+target_field_name+".value");		if (field_value != target_field_value) {			// figure out the target field's label so we can display it in the report			target_field_index = get_position(target_field_name, validate_settings);			target_field_label = validate_settings[target_field_index + 1];			validate_report += field_label + " and " + target_field_label + " should match\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// specify which characters are allowed in a field// allowed_1234. allows only the numbers 1, 2, 3, 4 and a dot	function allowed(field_value,allowed_characters) {		eval("field_value = field_value.replace(/["+eval_escape(regex_escape(allowed_characters))+"]/g,'')");		if (field_value != "") {			validate_report += field_label + " can't contain the characters '" + eval_escape(field_value) + "'\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// specify which characters are required in a field// required_a\n requires a letter a or a line break	function required(field_value,required_characters) {		eval("characters_present = field_value.search(/["+eval_escape(regex_escape(required_characters))+"]/)");		if (characters_present == -1) {			validate_report += field_label + " must contain at least one of the characters '" + eval_escape(required_characters) + "'\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}	// check for a length range or list of possible lengths (counts letters and numbers only)// also counts the length of a checkbox array// length_6 requires 6 characters, length_1-12 requires between 1 and 12 characters, length_1|3|5 requires 1, 3, or 5 characters	function lengthx(field_value,is_array,length_settings) {		error = 0;				if (is_array) {	// checkbox group			field_value_array = field_value.split(",");			field_value_length = field_value_array.length;			if (field_value == "") { field_value_length = 0; }					} else {			field_value = field_value.replace(/\W/g,""); // ignore punctuation			field_value_length = field_value.length;		}						if (length_settings.indexOf("-") != -1) {	// length_1-12 requires between 1 and 12 characters			length_settings = length_settings.split("-");			min = parseInt(length_settings[0]);			max = parseInt(length_settings[1]);			if ((field_value_length < min)||(field_value_length > max)) {				if (is_array) {					validate_report += field_label + " should have between " + min + " and " + max + " items selected\n";				} else {					validate_report += field_label + " should be between " + min + " and " + max + " characters long\n";				}				error = 1;			}					} else if (length_settings.indexOf("|") != -1) {	// length_1|3|5 requires 1, 3, or 5 characters			length_settings = length_settings.split("|");			success = 0;			oks = "";			for (k=0; k<length_settings.length; k++) {				ok = parseInt(length_settings[k]);				if (field_value_length == ok) {					success = 1;					break;				}				if (k < length_settings.length - 1) {					if (length_settings.length > 2) {						oks += ok + ", ";					} else {						oks += ok + " ";					}				} else {					oks += "or " + ok;				}			}			if (success == 0) {					if (is_array) {					validate_report += oks + " " + field_label + " should be selected\n";				} else {					validate_report += field_label + " should be " + oks + " characters long\n";				}				error = 1;			}					} else {			ok = parseInt(length_settings);			if (field_value_length != ok) {				if (is_array) {					validate_report += ok + " " + field_label + " should be selected\n";				} else {					validate_report += field_label + " should be " + ok + " characters long\n";				}				error = 1;			}		}				if (error == 1) {			if (! field_name_focus) {				if (is_array) {					if (array_length > 0) {						field_name_focus = "elements['" + field_name + "']" + "[0]";					} else {						field_name_focus = "elements['" + field_name + "']";					}				} else {					field_name_focus = field_name;				}			}		}	}// check for various requirements of a valid email address	function email_characters(field_value) {					// checks for legal characters (@-._aZ09), characters that can't start the address,		// characters that can't be together, only one @ symbol, legal characters after the @ (aZ09.-)		check_general = /[^@\-\.\w_]|^[_@\.\-]|[@\._\-]{2}|(@)[^@]*\1|@[\w\-.]{1,}_/;				// checks for at least one a-Z or 0-9 between the @ and the .		check_domain = /@[\w\-]{1,}\./;				// checks for two-, three- or four- character TLD (e.g. .de, .com, .info)		check_TLD = /\.[a-zA-Z]{2,4}$/;		if (((field_value.search(check_general) != -1)||(field_value.search(check_domain)) == -1)||(field_value.search(check_TLD) == -1)) {			validate_report += field_label + " does not contain a valid email address\n";			if (! field_name_focus) { field_name_focus = field_name; }		}		}// check for the proper lengths and required characters of each type of credit card (ignore punctuation)// this assumes a field named card_type is also present on the form	function credit_card(field_value,card_type) {		if ((card_type != "")&&(card_type.indexOf("...") == -1)) {			field_value = field_value.replace(/[^\d]/g,"");  // remove all but numbers			success = 1;			switch (card_type.substr(0,3).toLowerCase()) {				case "vis" :					// Visa: 16 or 13 digits long, first digit is 4					if (!((field_value.length == 16)||(field_value.length == 13))||!(parseInt(field_value.substr(0,1)) == 4)) {						success = 0;					}					break;				case "mas" :					// Mastercard: 16 digits long, first digit is 5, second digit is between 1 and 5 inclusive					if (!(field_value.length == 16)||!((parseInt(field_value.substr(0,1)) == 5)&&(parseInt(field_value.substr(1,1)) >= 1)&&(parseInt(field_value.substr(1,1)) <= 5))) {						success = 0;					}					break;				case "dis" :					// Discover: 16 digits long, first four digits are 6011					if (!(field_value.length == 16)||!(parseInt(field_value.substr(0,4)) == 6011)) {						success = 0;					}					break;				case "ame" :					// American Express: 15 digits long, first digit is 3, second digit is 4 or 7					if (!(field_value.length == 15)||!((parseInt(field_value.substr(0,1)) == 3)&&((parseInt(field_value.substr(1,1)) == 4)||(parseInt(field_value.substr(1,1)) == 7)))) {						success = 0;					}					break;				case "din" :					// Diner's Club: 14 digits long, first digit is 3, second digit is 0, 6, or 8					if (!(field_value.length == 14)||!((parseInt(field_value.substr(0,1)) == 3)&&((parseInt(field_value.substr(1,1)) == 0)||(parseInt(field_value.substr(1,1)) == 6)||(parseInt(field_value.substr(1,1)) == 8)))) {						success = 0;					}					break;				case "car" :					// Carte Blanche: 14 digits long, first digit is 3, second digit is 0, 6, or 8					if (!(field_value.length == 14)||!((parseInt(field_value.substr(0,1)) == 3)&&((parseInt(field_value.substr(1,1)) == 0)||(parseInt(field_value.substr(1,1)) == 6)||(parseInt(field_value.substr(1,1)) == 8)))) {						success = 0;					}					break;				case "enr" :					// EnRoute: 15 digits long, first four digits are 2014 or 2149					if (!(field_value.length == 15)||!((parseInt(field_value.substr(0,4)) == 2014)||(parseInt(field_value.substr(0,4)) == 2149))) {						success = 0;					}					break;				case "jcb" :					// JCB: 16 digits long, first four digits are 3088, 3096, 3112, 3158, 3337, or 3528					if (!(field_value.length == 16)||!((parseInt(field_value.substr(0,4)) == 3088)||(parseInt(field_value.substr(0,4)) == 3096)||(parseInt(field_value.substr(0,4)) == 3112)||(parseInt(field_value.substr(0,4)) == 3158)||(parseInt(field_value.substr(0,4)) == 3337)||(parseInt(field_value.substr(0,4)) == 3528))) {						success = 0;					}					break;				default : 					validate_report += card_type + " is not a valid credit card type\n";					break;			}			if (! success) {				validate_report += field_label + " is not a valid " + card_type + " number\n";				if (! field_name_focus) { field_name_focus = field_name; }			}		} //else { // we probably don't need this because we'll have a not_blank on the card type field		//	validate_report += field_label + " needs an associated card type\n";		//}	}// check if the value has a valid tax exempt ID number format (Exxxx-xxxx-xx)	function taxexempt_format(field_value) {		field_value = field_value.replace(/E\d{4}-\d{4}-\d{2}/,"okay");		if (field_value != "okay") {			validate_report += field_label + " is not a valid tax exempt ID\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}// check if the value has a valid social security number format (xxx-xx-xxxx)	function SSN_format(field_value) {		field_value = field_value.replace(/\d{3}-\d{2}-\d{4}/,"okay");		if (field_value != "okay") {			validate_report += field_label + " is not a valid SSN\n";			if (! field_name_focus) { field_name_focus = field_name; }		}	}	// return the texts of the selected menu items	function get_menu_text(form_name, field_name) {		eval("this_element = document."+form_name+"."+field_name+".options");		selected_texts = new Array();		for (k=0; k<this_element.length; k++) {			if (this_element[k].selected) {				//selected_texts.push(this_element[k].text); // why doesn't this line work? ("object doesn't support...")				selected_texts[selected_texts.length] = this_element[k].text;			}		}		selected_texts = selected_texts.join(",");		return selected_texts;	}// return the values of the selected menu items (not_blank works on some, but not all, browsers, so this is safer)	function get_menu_value(form_name, field_name) {		eval("this_element = document."+form_name+".elements['"+field_name+"'].options"); // the extra bit here allows us to use names that contain [] without throwing syntax errors		selected_values = new Array();		for (k=0; k<this_element.length; k++) {			if (this_element[k].selected) {				//selected_values.push(this_element[k].value); // why doesn't this line work? ("object doesn't support...")				selected_values[selected_values.length] = this_element[k].value;			}		}		selected_values = selected_values.join(",");		return selected_values;	}// return the value of the selected radio button	function get_radio_value(form_name, field_name) {		eval("this_element = document."+form_name+".elements['"+field_name+"']"); // the extra bit here allows us to use names that contain [] without throwing syntax errors		selected_value = "";		array_length = this_element.length; // the not_blank functions will use this, too		if (array_length > 0) {			for (k=0; k<array_length; k++) {				if (this_element[k].checked) {					selected_value = this_element[k].value;					break;				}			}		} else {			if (this_element.checked) {				selected_value = this_element.value;			}		}		return selected_value;	}// return the values of the selected checkboxes as a comma-delimited list	function get_checkbox_value(form_name, field_name) {		eval("this_element = document."+form_name+".elements['"+field_name+"']"); // the extra bit here allows us to use names that contain [] without throwing syntax errors		selected_values = new Array();		array_length = this_element.length; // the not_blank functions will use this, too		if (array_length > 0) {			for (k=0; k<array_length; k++) {				if (this_element[k].checked) {					//selected_values.push(this_element[k].value); // why isn't this line working? ("object doesn't support...")					selected_values[selected_values.length] = this_element[k].value;				}			}		} else {			if (this_element.checked) {				selected_values[0] = this_element.value;			}		}		selected_values = selected_values.join(",");		return selected_values;	}	// escape any metacharacters in the string so we don't accidentally evaluate them as regular expression symbols		function regex_escape(string) {		string = string.replace(/\\/g, "\\\\");		string = string.replace(/\./g, "\\.");		string = string.replace(/\+/g, "\\+");		string = string.replace(/\*/g, "\\*");		string = string.replace(/\?/g, "\\?");		string = string.replace(/\[/g, "\\[");		string = string.replace(/\^/g, "\\^");		string = string.replace(/\]/g, "\\]");		string = string.replace(/\$/g, "\\$");		string = string.replace(/\(/g, "\\(");		string = string.replace(/\)/g, "\\)");		string = string.replace(/\{/g, "\\{");		string = string.replace(/\}/g, "\\}");		string = string.replace(/\//g, "\\/");		return string;	}		// escape any line breaks in the string so we can use them in alerts and evals		function eval_escape(string) {		string = string.replace(/"/g, "\\\"");		string = string.replace(/'/g, "\\'");		string = string.replace(/\n/g, "\\n");		string = string.replace(/\r/g, "\\r");		return string;	}	/*	would this be a better way to format the validate_settings variable?		validate_settings = "				name: not_blank, letters_only;				phone: not_blank, numbers_only;				email: not_blank, email;				card_number: not_blank, numbers_only;			"*/
