/*********************************************************************************************************************
 * validate.js																																			*
 *																																							*
 * This file takes on the role of client-side form validation.  There are certain regular expressions that fields		*
 * must pass in order to be valid.  The page only affects form fields that contain a valid validation mark-up as		*
 * identified below.  It should be noted that there is a validation.php file that compliments this javascript.			*
 *********************************************************************************************************************/

function validateForm(elm) {
	// All regular expressions to check against
	var regex_username = /^[A-Za-z](?=[A-Za-z0-9_.]{4,29}$)[a-zA-Z0-9_]*\.?[a-zA-Z0-9_]*$/;
	var regex_password = /^(?=^.{6,30}$)((?=.*[A-Za-z0-9])(?=.*[A-Z])(?=.*[a-z]))^.*$/;
	var regex_alpha = /^[a-zA-Z\s]+/;
	var regex_num = /^[0-9\s]+$/;
	var regex_alphanum = /^[a-zA-Z0-9\s.\-\_\']+$/;
	var regex_date = /(0[1-9]|[12][0-9]|3[01])[- /.](0[1-9]|1[012])[- /.](19|20)[0-9]{2}/;
	var regex_email = /^[\w-]+(\.[\w-]+)*@([a-z0-9-]+(\.[a-z0-9-]+)*?\.[a-z]{2,6}|(\d{1,3}\.){3}\d{1,3})(:\d{4})?$/;
	var regex_zip = /^([0-9]{5}(?:-[0-9]{4})?)*$/;
	var regex_ns = /\s+/;
	var regex_url = /^(http|https|ftp)\:\/\/([a-zA-Z0-9\.\-]+(\:[a-zA-Z0-9\.&amp;%\$\-]+)*@)*((25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9])\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[1-9]|0)\.(25[0-5]|2[0-4][0-9]|[0-1]{1}[0-9]{2}|[1-9]{1}[0-9]{1}|[0-9])|localhost|([a-zA-Z0-9\-]+\.)*[a-zA-Z0-9\-]+\.(com|edu|gov|int|mil|net|org|biz|arpa|info|name|pro|aero|coop|museum|[a-zA-Z]{2}))(\:[0-9]+)*(\/($|[a-zA-Z0-9\.\,\?\'\\\+&amp;%\$#\=~_\-]+))*$/;
	var posted_data = new Array();
	var numErrors = 0;

	/*********************************************************************************************************************
	 * The below code will go through all form elements submitted and validate each one accordingly. It uses the form		*
	 * field's name as a method of checking which type of validation, if any, needs to be executed. The form field name	*
	 * must include _val{options} to be a valid form of validation.																		*
	 * IE. <input type="text" name="myname_val{option1_option2_option3}" />																*
	 *																																							*
	 * LIST OF VALID OPTIONS																															*
	 * 	required					= field is a required field and cannot be left blank													*
	 * 	len(x,y)					= field must be a certain length.  x is min length, y is max length.  Set y = -1 for		*
	 *									  unlimited max length.  IE len(4,-1) will result in at least 4 characters long				*
	 *									  IMPORTANT!!!  NO SPACE BETWEEN X,Y ALLOWED!!!															*
	 *		username					= field is a username, so validate is as indicated above under $regex_username				*
	 *		password					= field is a password, so validate is as indicated above under $regex_password				*
	 *		alpha						= field contains only letters and spaces																	*
	 *		num						= field contains only digits and spaces																	*
	 *		alphanum					= field contains digits, letters, spaces, underscores, hyphens, apostraphe, periods			*
	 *		email						= field is a valid email address.																			*
	 *		date						= field is a valid date: mm/dd/yyyy																			*
	 *		daterange				= field is a valid date range: mm/dd/yyyy - mm/dd/yyyy												*
	 *		phone						= field is a valid US phone number.  Letter phones are permitted.  IE. 1-800-FLOWERS		*
	 *		zip						= field is a valid US or Canadian zip code... 5 and 5+4 zips allowed								*
	 *		url						= field is a valid URL address																				*
	 *		ns							= field CANNOT contain any spaces.  Use this in conjunction with alpha, num, or alphanum	*
	 *		find(tbl_name)			= special case to hit against the database and search a given table for the value entered	*
	 *									  will return error if value is not found.																*
	 *		taken(tbl_name)		= special case to hit against the database and search a given table for the value entered	*
	 *									  will return error if value is found.																		*
	 *																																							*
	 *	PLEASE NOTE: if recaptcha is used in your form (http://www.google.com/recaptcha) then it will automatically be		*
	 * checked properly so long as you've entered the proper private and public keys into the "constants.php" file			*
	 * located under the "backend" directory.																										*
	 *********************************************************************************************************************/

	// go through each form element submitted.
	$(elm+':input').each(function() {
		// set up validation variables
		field = '';
		pre_valstring = '';
		valstring = new Array();
		start = -999;
		end = -999;
		iserror = false;
		isempty = false;
		errormsg = '';
	
		// special check for any reCaptcha form fields
		if(this.name == 'recaptcha_response_field') {
			field = 'recaptcha_response_field';
			// if the recaptcha field is empty
			if(this.value == '' || this.value.trim().length == 0) {
				errormsg = '<div class="error"><p>This is required and cannot be blank</p></div>';
				numErrors++;
				iserror = true;
			// check to see if the value entered matches the challenge text using AJAX
			}else {
				numErrors++;
				response = $.ajax({
					url: 'backend/ajax.php?action=recaptcha',
					type: 'POST',
					async: false,
					data: ({ recaptcha_challenge_field: $('#recaptcha_challenge_field').val(), recaptcha_response_field: $('#recaptcha_response_field').val() })
				}).responseText;
				if(response == 0) {
					numErrors--;
					// Set a hidden input field to 1 so that the php validation does not check it again, or it will fail
					$('#recaptcha_validated_field').val('1');
				// What happens when the CAPTCHA was entered incorrectly
				}else {
					errormsg = '<div class="error"><p>The words were not identified correctly</p></div>';
				}
			}
		}	
		// check to see if the form field is going to be validated.  To do this, we look for _val{ in the form field name.
		if(this.name.indexOf('_val{') > -1) {
			start = this.name.indexOf('_val{');
		}
		// make sure the form field name includes the closing bracket }
		if(this.name.indexOf('}') > -1) {
			end = this.name.substr(start+5).length-1;
		}
		// if a form field name contains a valid "_val{}" markup, validate the field
		if(start != -999 && end != -999) {
			// get the field's name
			field = this.name.substr(0, start);
			// get the validation string
			pre_valstring = this.name.substr(start+5, end);
			// break the validation string up into an array.  Each validation option should be separated by an underscore
			pre_valstring = pre_valstring.split('_');
			// check and see if there is anything entered to validate!  If not, set a flag... this is common on non-required entries
			if(this.value == '' || this.value.trim().length == 0) {
				isempty = true;
			}
			// check and see if the field is required... if so, make sure the field is not blank or set a flag
			for(i=0; i<pre_valstring.length; i++) {
				if(pre_valstring[i] == 'required') {
					if(isempty) {
						errormsg = '<div class="error"><p>This is required and cannot be blank</p></div>';
						numErrors++;
						iserror = true;
					}
				}else {
					valstring[i] = pre_valstring[i];
				}
			}
			// only run the rest of the validation options if the field is not required and is not empty, or is required, not empty and didn't produce a required error.
			if(!iserror && !isempty) {
				// go through the rest of the array of options and execute each validation rule as applicable
				for(i=0; i<valstring.length; i++) {
					// check to see if the validation rule is len() because we have to handle that differently
					if(valstring[i].substr(0, 3) == 'len') {
						temp = valstring[i].substr(4);
						temp = temp.substr(0, temp.length-1);
						temp = temp.split(',');
						minamount = temp[0];
						maxamount = temp[1];
						// if this.value is a checkbox, then we must check the rest of the checkboxes associated with this one,
						// since a physical array of elements doesn't get posted to this page
						if(this.type == 'checkbox') {
							total_len = findNumberChecked(field);
							if(this.checked) {
								total_len++;
							}
							errmsg1 = 'select';
							errmsg2 = 'option(s)';
						// otherwise it's a string, so check it accordingly
						}else {
							total_len = this.value.trim().length;
							errmsg1 = 'be';
							errmsg2 = 'character(s) long';
						}
						if(minamount > 0 && maxamount == -1) {
							if(total_len < minamount) {
								errormsg = '<div class="error"><p>Must '+errmsg1+' at least '+minamount+' '+errmsg2+'</p></div>';
								numErrors++;
							}
						}else if(minamount == 0 && maxamount >= 1) {
							if(total_len > maxamount) {
								errormsg = '<div class="error"><p>Must '+errmsg1+' up to '+maxamount+' '+errmsg2+'</p></div>';
								numErrors++;
							}
						}else if(minamount == maxamount) {
							if(total_len != maxamount) {
								errormsg = '<div class="error"><p>Must '+errmsg1+' exactly '+maxamount+' '+errmsg2+'</p></div>';
								numErrors++;
							}
						}else if(minamount >= 0 && maxamount > 0) {
							if(total_len < minamount || total_len > maxamount) {
								errormsg = '<div class="error"><p>Must '+errmsg1+' between '+minamount+' and '+maxamount+' '+errmsg2+'</p></div>';
								numErrors++;
							}
						}
					// Check to see if the validation rule is find(tbl_name) because we handle that differently
					}else if(valstring[i].substr(0, 4) == 'find') {
						numErrors++;
						var tbl = valstring[i].substr(5, valstring[i].length-6);
						var dataStr = field+'='+this.value+'&tbl='+tbl;
						var response = $.ajax({
							url: 'backend/ajax.php?action=find',
							type: 'POST',
							async: false,
							data: dataStr
						}).responseText;
						if(response == 1) {
							numErrors--;
						}else {
							errormsg = '<div class="error"><p>'+field.charAt(0).toUpperCase()+field.slice(1)+' was not found</p></div>';
						}
					// Check to see if the validation rule is taken(tbl_name) because we handle that differently
					}else if(valstring[i].substr(0, 5) == 'taken') {
						numErrors++;
						var tbl = valstring[i].substr(6, valstring[i].length-7);
						var dataStr = field+'='+this.value+'&tbl='+tbl;
						if($('#username').length > 0) {
							dataStr += '&username='+$('#username').val();
						}
						response = $.ajax({
							url: 'backend/ajax.php?action=taken',
							type: 'POST',
							async: false,
							data: dataStr
						}).responseText;
						if(response == 1) {
							numErrors--;
						}else {
							errormsg = '<div class="error"><p>'+field.charAt(0).toUpperCase()+field.slice(1)+' is already in use</p></div>';
						}
					// otherwise, switch through the other options and validate accordingly
					}else {
						switch (valstring[i]) {
							case 'username':
								if(!regex_username.test(this.value)) {
									errormsg = '<div class="error"><p>Username does not meet minimum requirements.  Hover over the ? for help</p></div>';
									numErrors++;
								}
								break;
							case 'password':
								if(!regex_password.test(this.value)) {
									errormsg = '<div class="error"><p>Password does not meet minimum requirements.  Hover over the ? for help</p></div>';
									numErrors++;
								}
								break;
							case 'alpha':
								if(!regex_alpha.test(this.value)) {
									errormsg = '<div class="error"><p>Must only contain letters</p></div>';
									numErrors++;
								}
								break;
							case 'num':
								if(!regex_num.test(this.value)) {
									errormsg = '<div class="error"><p>Must only contain numbers</p></div>';
									numErrors++;
								}
								break;
							case 'alphanum':
								if(!regex_alphanum.test(this.value)) {
									errormsg = '<div class="error"><p>Must only contain letters, numbers, dashes, underscores, or hyphens</p></div>';
									numErrors++;
								}
								break;
							case 'email':
								if(!regex_email.test(this.value)) {
									errormsg = '<div class="error"><p>Must be a valid email address</p></div>';
									numErrors++;
								}
								break;
							case 'date':
								if(!regex_date.test(this.value)) {
									errormsg = '<div class="error"><p>Must be a valid date: mm/dd/yyyy</p></div>';
									numErrors++;
								}
								break;
							case 'daterange':
								// Since we are working with a date range, there should be two dates supplied
								daterange = this.value.split('-');
								if(daterange.length != 2 && !regex_date.test(daterange[0]) && !regex_date.test(daterange[1])) {										
									errormsg = '<div class="error"><p>Must be a valid date: mm/dd/yyyy</p></div>';
									numErrors++;
								}
								break;
							case 'phone':
								newphone = formatPhone(this.value);
								if(newphone == false) {
									errormsg = '<div class="error"><p>Must be a valid phone number</p></div>';
									numErrors++;
								}else {
									this.value = newphone;
								}
								break;
							case 'zip':
								if(!regex_zip.test(this.value)) {
									errormsg = '<div class="error"><p>Must be a valid zip code</p></div>';
									numErrors++;
								}
								break;
							case 'ns':
								if(regex_ns.test(this.value)) {
									errormsg = '<div class="error"><p>Must not contain any spaces</p></div>';
									numErrors++;
								}
								break;
						}
					}
				}
			}
		}
		// Show any error on the page
		if(errormsg.length > 0) {
			field = field.replace(/\[\]/, '');
			$('#'+field+'_error').show();
			$('#'+field+'_error').html(errormsg);
		// Hide any error on the page, in the event that the error was corrected
		}else {
			field = field.replace(/\[\]/, '');
			$('#'+field+'_error').hide();
			$('#'+field+'_error').html('');
		}
	});
	// If no errors exist on the entire page, hide the message area and submit the form
	if(numErrors == 0) {
		if($('.msgArea').length > 0) {
			$('.msgArea').html('');
		}
		return true;
	// Otherwise, show the message area and do not submit the form.
	}else {
		if($('.msgArea').length > 0) {
			$('.msgArea').html('<div class="ui-state-error ui-corner-all" style="padding: 0 .7em;"><p><span class="ui-icon ui-icon-alert" style="float: left; margin-right: .3em;"></span><strong>Oops... We found '+numErrors+' issue(s).</strong> Please review the form and correct all issues as indicated below.</p></div>');
			removeSysMsg();
		}
		$('.submit_btn').removeClass('ui-state-focus');
		return false;
	}
}
<!--- FUNCTION TO FORMAT A GIVEN PHONE NUMBER TO X-XXX-XXX-XXXX --->
function formatPhone(phone) {
	// Strip out any extra characters that we do not need only keep letters and numbers
	phone = phone.toUpperCase().replace(/[^0-9A-Za-z]/g, '');
	// 7 digit phone number format
	if(phone.length == 7) {
		// If there are letters at the end, don't break them apart
		if(phone.match(/([A-Z]{7})/)) {
			return phone.replace(/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '$1$2');
		// There are no letters at the end
		}else {
			return phone.replace(/([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '$1-$2');
		}
	// 10 digit phone number format
	}else if(phone.length == 10) {
		// If there are letters at the end, don't break them apart
		if(phone.match(/([0-9a-zA-Z]{3})([A-Z]{7})/)) {
			return phone.replace(/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '($1)-$2$3');
		// There are no letters at the end
		}else {
			return phone.replace(/([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '($1)-$2-$3');
		}
	// 11 digit phone number format
	}else if(phone.length == 11) {
		// If there are letters at the end, don't break them apart
		if(phone.match(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([A-Z]{7})/)) {
			return phone.replace(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '$1-($2)-$3$4');
		// There are no letters at the end
		}else {
			return phone.replace(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})/, '$1-($2)-$3-$4');
		}
	// Unknown digit length phone number format
	}else {
		// If there are letters at the end, don't break them apart
		if(phone.match(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([A-Z]{7})/)) {
			return phone.replace(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})([0-9a-zA-Z])/, '$1-($2)-$3$4 $5');
		// There are no letters at the end
		}else {
			return phone.replace(/([0-9a-zA-Z]{1})([0-9a-zA-Z]{3})([0-9a-zA-Z]{3})([0-9a-zA-Z]{4})([0-9a-zA-Z])/, '$1-($2)-$3-$4 $5');
		}
	}
}
<!--- FUNCTION TO TEST IF A VALUE IS AN ARRAY --->
function findNumberChecked(objName) {
var arr = new Array();
	var numChecked = 0;
	arr = document.getElementsByName(objName);
	for(var i = 0; i < arr.length; i++) {
		var obj = document.getElementsByName(objName).item(i);
		if(document.getElementsByName(objName).item(i).checked) {
			numChecked++;
		}
	}
	return numChecked;
}
<!--- FUNCTION TO TRIM WHITESPACE --->
String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g,'');
}
