/******************************************************************************
********** validate.js
********** 
********** A library which provides dynamic client side form validation
********** for a variety of common data types.
**********
********** Current tests include:
********** 
********** 1) Email validation
********** 2) Phone number (optional area code)
********** 3) Zip Code (optional 4 digit extension)
********** 4) Social Security
********** 5) empty (nil) entry validation
******************************************************************************/

/******************************************************************************
********** form_validate(form) 
********** 
********** Call this function directly from the html form.
********** Example Usage: onSubmit="return form_validate(this)"
**********
********** Returns true is form passes validation, false if it fails.
********** If validation fails will present a popup dialog box listing
********** errors to be corrected.
******************************************************************************/
function form_validate(form)
{
	// gather for validation requirements
	var reqs = get_reqs(form);
	var err_found = false;
	// this will be the error message (if there are errors)
	var alert_message = new String();

	for (var i = 0; i < reqs.length; i++) {
		// gather form element type - required for select types
		var sel_str = "form." + reqs[i].name + ".type";
		var sel_typ = eval(sel_str);
		// the value of the form element
		var val;

		if ((sel_typ == "select-one") || (sel_typ == "select-multiple")) { 
			var el_name = reqs[i].name; // element name
			var index = eval("form." + el_name + ".selectedIndex");
			// nothing was selected in multiple select - same as nil
			if (index == -1) {
				val = "nil";	
			} else {
				val = eval("form." + el_name + ".options[" + index + "].value");
			}
		} else {
			val = eval("form." + reqs[i].name + ".value");
		}

		var req  = reqs[i].required;
		var res  = reqs[i].response;
		var type = reqs[i].type;
		switch (type) {
			case "email" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_email(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "phone_number" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_phone_number(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "area_code" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_phone_number_area_code(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "phone_prefix" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_phone_number_prefix(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "phone_suffix" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_phone_number_suffix(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "social_security" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_social_security_number(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			case "zip_code" :
				if ((val=="" && req=="true") || 
				    (val!="" && is_valid_zip_code(val)==false)) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
			default: 
				if (req=="true" && (is_nil(val)==true || val=="")) {
					alert_message += res + "\n";
					err_found = true;
				}
				break;
		}
	}
	// At least one form field has errors. Give a message and return.
	if (err_found) {
		var pref, suff;
		// the user may want to use their own error prefix
		if (form.error_prefix) {
			pref = form.error_prefix.value + "\n\n";
		} else {
			pref = "The following errors were found:\n\n"
		}
		
		// the user may want to use their own error suffix
		if (form.error_suffix) {
			suff = "\n" + form.error_suffix.value;
		} else {
			suff = "\nPlease correct and resubmit.";
		}

		alert_message = pref + alert_message + suff;
		alert(alert_message);
		return false;
	}

	return true;
}

/******************************************************************************
********** Requirements() 
********** 
********** An Object providing convenient access to form validation routines.
******************************************************************************/
function Requirements()
{
	this.name = "";
	this.type = "";
	this.required = "";
	this.response = "";
}

/******************************************************************************
********** get_reqs(form)
********** 
********** Gather validation requirements from an html form.
**********
********** Returns an Array of Requirements Objects
******************************************************************************/
function get_reqs(form)
{
	var reqs = new Array();
	
	for (var i = 0,j=0; i < form.length; i++) {
		// grab the element name and value
		var name = form.elements[i].name;
		var value = form.elements[i].value;
		// check to see if it is a validation rule
		var rx = /validate/
		if (rx.test(name)) {
			// it is a rule - parse the elements
			reqs[j] = new Requirements();
			var splits = value.split(",")
			reqs[j].name      = splits[0];
			reqs[j].type      = splits[1];
			reqs[j].required  = splits[2];
			// there may be commas in the user's response string.
			// concatenate the elements.
			var k = 3;
			for (k = 3; k < splits.length-1; k++) {
				// reappend the commas which were removed in the split
				reqs[j].response  += splits[k] + ",";
			}
			// the last element has no comma - merely append
			reqs[j].response += splits[k];
			++j;
		}
	}

	return reqs;
}

/******************************************************************************
********** is_nil(content)
********** 
********** Determines whether an form element is Nil (contains the value 
********** 'nil').  
********** 
********** Returns true if the value is nil, false otherwise.
******************************************************************************/
function is_nil(content)
{
	// not nil
	rx = /^[nN][iI][lL][lL]?$/
	return rx.test(content);
}

/******************************************************************************
********** is_valid_email(email) 
********** 
********** Returns true if the email has a valid syntax, false otherwise. 
******************************************************************************/
function is_valid_email(email)
{
	// [text.]text@text.text[.text]
	var rx = /^[\w.]*\w+@\w+\.\w+[\w.]*$/
	return rx.test(email);
}

/******************************************************************************
********** is_valid_phone_number(num)
********** 
********** Returns true if the number is a valid phone number,
********** false otherwise.
******************************************************************************/
function is_valid_phone_number(num)
{
	// (999) 555-1212 or 555-1212
	var rx = /^(\(?\d{3}\)?[-. ]?)?\d{3}[-. ]?\d{4}$/
	return rx.test(num);
}

/******************************************************************************
********** is_valid_phone_number_area_code(num)
********** 
********** Returns true if num is a valid area code, false otherwise.
******************************************************************************/
function is_valid_phone_number_area_code(num)
{
	// (310) or 310
	var rx = /^\(?\d{3}\)?$/
	return rx.test(num);
}

/******************************************************************************
********** is_valid_phone_number_prefix(num)
********** 
********** Returns true if num is a valid phone number prefix, false otherwise.
******************************************************************************/
function is_valid_phone_number_prefix(num)
{
	// 555
	var rx = /^\d{3}$/
	return rx.test(num);
}

/******************************************************************************
********** is_valid_phone_number_suffix(num)
********** 
********** Returns true if num is a valid phone number suffix, false otherwise.
******************************************************************************/
function is_valid_phone_number_suffix(num)
{
	// 1212
	var rx = /^\d{4}$/
	return rx.test(num);
}

/******************************************************************************
********** is_valid_social_security_number(num)
********** 
********** Returns true if num is a valid social security  number, 
********** false otherwise.
******************************************************************************/
function is_valid_social_security_number(num)
{
	// 123-45-6789 or 123456789
	var rx = /^\d{3}-?\d{2}-?\d{4}$/
	return rx.test(num);
}

/******************************************************************************
********** is_valid_zip_code(num)
********** 
********** Returns true if num is a valid zip code, false otherwise.
******************************************************************************/
function is_valid_zip_code(num)
{
	// 12345 or 12345-1234
	var rx = /^(\d{5})( ?-? ?\d{4})?$/
	return rx.test(num);
}
