/* **************************************************************************** */
/* *** jQuery-enabled Form Validation Script ********************************** **
       ©2007 by Adrienne L. Travis												
	   
	Usage:																		
	Apply the 'required' flag, as well as one or more other pattern flags, by	
	adding the class 'val-[PATTERN]' (that is, "val" followed by a dash  		
	followed by thepattern's clsid) to any form input, textarea, or select box.	
	The script also does a VERY basic check for SQL injection and scripting     
	attacks in any text input or textarea field. 			
	
** **************************************************************************** */
/* **************************************************************************** */

/* *** ^thePatterns Object Structure ****************************************** */

var thePatterns = new Object;

/* ----------------------------------------------------------------------------	**										
	thePatterns is an object that holds nested objects, one per validation 		
	pattern. Construct these nested objects as follows:							
																			
	declare your object: 														
		thePatterns.isWhatever = new Object;  								
	declare the CSS class name (will be prefixed by val- in the HTML and CSS):	
		thePatterns.isWhatever.clsid = "whatever";							
	declare the (Regular Expression) pattern to match against:					
		thePatterns.isWhatever.pattern = "^(something)$";	
	declare the regular expression MODIFIERS, if any, to use: 
		thePatterns.isWhatever.patternmod = "";
	declare the text of the error that will be displayed if invalid:				
		thePatterns.isWhatever.errortxt = "some error text";					
	(OPTIONAL) declare a function to be run on SUCCESSFUL validation, which     
	takes as input the field ID being validated: 								
 		thePatterns.isNumeric.extrafunc = function(fid) { }						
	The idea of this extra function is that (a) it can handle programmatic 		
	validation that can't be handled by regular expression (such as credit card
	number validation), as well as reformatting that you might want done to     
	normalize input. (Though this could just as well be done on submit, and 
	doing it before submit might confuse the user. Use that idea with caution!)
** ----------------------------------------------------------------------------	*/
	
	thePatterns.isNumeric = new Object;
	thePatterns.isNumeric.clsid = "numeric";
	thePatterns.isNumeric.pattern = "^-?(?:\\d+|\\d{1,3}(?:,\\d{3})+)(?:\\.\\d+)?$";
	thePatterns.isNumeric.patternmod = "";
	thePatterns.isNumeric.errortxt = "The value of this field must be a number";
	thePatterns.isNumeric.extrafunc = function(fid) 
		{ 
		var vtemp = $("#"+fid).val();
		vtemp = vtemp.replace(/,/g,"");
		$("#"+fid).val(vtemp);
		};

	thePatterns.isCurrency = new Object;
	thePatterns.isCurrency.clsid = "currency";
	thePatterns.isCurrency.pattern = "^(-)?([\\$\\u20AC\\u00A3])?((\\d+)|(\\d{1,3})(\\,\\d{3})*)(\\.\\d{1,2})?$";
	thePatterns.isCurrency.patternmod = "";
	thePatterns.isCurrency.errortxt = "The value of this field must be a currency value (no more than two decimal places).";
	thePatterns.isCurrency.extrafunc = function(fid) 
		{ 
		var vtemp = $("#"+fid).val();
		vtemp = vtemp.replace(/(\$|,)/g,"");
		$("#"+fid).val(vtemp);
		};
	
	thePatterns.isPercentage = new Object;
	thePatterns.isPercentage.clsid = "percentage";
	thePatterns.isPercentage.pattern = "^(100(?:\\.0{1,2})?|0*?\\.\\d{1,2}|\\d{1,2}(?:\\.\\d{1,2})?)(%)?$";
	thePatterns.isPercentage.patternmod = "";
	thePatterns.isPercentage.errortxt = "Please enter a percentage between 0 and 100 (no more than two decimal places).";
	
	thePatterns.isTime = new Object;
	thePatterns.isTime.clsid = "time";
	thePatterns.isTime.pattern = "^([0-1]?[0-9]|2[0-3]):[0-5][0-9](\s)?(AM|PM)?$";
	thePatterns.isTime.patternmod = "";
	thePatterns.isTime.errortxt = "Please enter a valid time.";
		
	thePatterns.isSSN = new Object;
	thePatterns.isSSN.clsid = "ssn";
	thePatterns.isSSN.pattern = "(^|\\s)(00[1-9]|0[1-9]0|0[1-9][1-9]|[1-6]\\d{2}|7[0-6]\\d|77[0-2])(-?|[\\. ])([1-9]0|0[1-9]|[1-9][1-9])\\3(\\d{3}[1-9]|[1-9]\\d{3}|\\d[1-9]\\d{2}|\\d{2}[1-9]\\d)$";
	thePatterns.isSSN.patternmod = "";
	thePatterns.isSSN.errortxt = "Please enter a valid Social Security Number.";
	thePatterns.isSSN.extrafunc = function(fid) 
		{ 
		var vtemp = $("#"+fid).val();
		vtemp = vtemp.replace(/(-|\s)/g,"");
		$("#"+fid).val(vtemp);
		};
	
	thePatterns.isPhone = new Object;
	thePatterns.isPhone.clsid = "phone";
	thePatterns.isPhone.pattern = "^((\\d[-. ]?)?((\\(\\d{3}\\))|\\d{3}))?[-. ]?\\d{3}[-. ]?\\d{4}$";
	thePatterns.isPhone.patternmod = "";
	thePatterns.isPhone.errortxt = "Please enter a valid US phone number.";
	
	thePatterns.isPassword = new Object;
	thePatterns.isPassword.clsid = "password";
	thePatterns.isPassword.pattern = "(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?!.*\\s).{4,8}";
	thePatterns.isPassword.patternmod = "";
	thePatterns.isPassword.errortxt = "Password must be 6-13 characters in length, and contain at least one lower case letter, one upper case letter, and one digit.";
	
	thePatterns.isEmail = new Object;
	thePatterns.isEmail.clsid = "email";
	thePatterns.isEmail.pattern = "[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+(?:[a-z]{2}|com|org|net|edu|mil|mobi|gov|biz|info|name|aero|info|jobs|museum)$";
	thePatterns.isEmail.patternmod = "i";
	thePatterns.isEmail.errortxt = "Please enter a valid email address";
	
	thePatterns.isCard = new Object;
	thePatterns.isCard.clsid = "creditcard";
	thePatterns.isCard.pattern = "^((?:4\\d{3})|(?:5[1-5]\d{2})|(?:6011)|(?:3[68]\d{2})|(?:30[012345]\\d))[ -]?(\\d{4})[ -]?(\\d{4})[ -]?(\\d{4}|3[4,7]\\d{13})$";
	thePatterns.isCard.patternmod = "i";
	thePatterns.isCard.errortxt = "Please enter a valid credit card number.";
	thePatterns.isCard.extrafunc = function(fid) 
		{ 
		var ccnum = $("#"+fid).val();
		// Remove all dashes for the checksum checks to eliminate negative numbers
		ccnum = ccnum.split("-").join("");
		// Checksum ("Mod 10")
		// Add even digits in even length strings or odd digits in odd length strings.
		var checksum = 0;
		for (var i=(2-(ccnum.length % 2)); i<=ccnum.length; i+=2) 
			{
			checksum += parseInt(ccnum.charAt(i-1));
			}
		// Analyze odd digits in even length strings or even digits in odd length strings.
		for (var i=(ccnum.length % 2) + 1; i<ccnum.length; i+=2) 
			{
			var digit = parseInt(ccnum.charAt(i-1)) * 2;
			if (digit < 10) 
				{ 
				checksum += digit; 
				} 
			else 
				{ 
				checksum += (digit-9); 
				}
			}
		if ((checksum % 10) != 0) 
			{
			$("#"+fid).addClass("errorEd");
			$("#"+fid).after('<label class="errorLabel" for="'+fid+'">'+"Please enter a valid credit card number."+"</label>");
			}
		};
	
	thePatterns.isZipCode = new Object;
	thePatterns.isZipCode.clsid = "zipcode";
	thePatterns.isZipCode.pattern = "(^(\\d{5}((-|\\s)?\\d{4})?)$)|(^([a-zA-Z][0-9][a-zA-Z]\\s?[0-9][a-zA-Z][0-9])$)";
	thePatterns.isZipCode.patternmod = "";
	thePatterns.isZipCode.errortxt = "Please enter a valid US or Canadian zip code";
	thePatterns.isZipCode.extrafunc = function(fid) { 
	   var vtemp = $("#"+fid).val();
	   if (vtemp.length == 9) 
			{
			var vzip = vtemp.substring(0,5)+"-"+vtemp.substring(5,9);
			$("#"+fid).val(vzip);
			} 
	   else if(vtemp.length == 10) 
			{
			vzip = vtemp.replace(" ","-");
			$("#"+fid).val(vzip);
			} 
	   else 
			{
			vzip = vtemp.toUpperCase();
			$("#"+fid).val(vzip);
			}
	};
/* *** end ^thePatterns Object Structure ************************************** */

/* *** ^MAIN LOOP ( ready() event attachment ) ******************************** */
	$(document).ready(function() {

/* --- @function declarations ------------------------------------------------- */
	
/* valField does all the heavy lifting. */
	function valField(fid) {
	
		//  What kind of field have we got? ('text' type or 'select' type) 
		//  Also get the value, and reset the error count.
		var errorcount = 0;
		
		var tlabel = $("label[@for="+fid+"]").text();
		if($("#"+fid).is("textarea")) {
		    var tlabeltemp = tlabel.split(":");
			var tlabel = tlabeltemp[0]+":";
		}
		
		if($("#"+fid).is("select")) 
			{
			var tval = $("#"+fid).children("[@selected]").val();
			var isselect = 1;
			var tlabeltemp = tlabel.split(":");
			var tlabel = tlabeltemp[0]+":";
			} 
		else 
			{
			var tval = $("#"+fid).val();
			tval = $.trim(tval);
			$("#"+fid).val(tval);
			var isselect = 0;
			}
		
		
		//  Run checks for required fields, for both 'text' and 'select' types.
		if($("#"+fid).is(".val-required")) 
			{
			if(tval == "") 
				{
				$("#"+fid).addClass("errorEd");
				if(isselect == 0) 
					{
					$("#errors").append('<label class="errorLabel reqLabel" for="'+fid+'"><strong>'+tlabel+"</strong> This field may not be left blank."+"</label>");
					} 
				else 
					{
					$("#errors").append('<label class="errorLabel reqLabel" for="'+fid+'"><strong>'+tlabel+"</strong> A value must be selected."+"</label>");
					}
				++errorcount;
				}
			}
		
		//  On 'text' types only, also check against all validation patterns
		if(isselect == 0) 
			{  
		    for (var i in thePatterns)
				{
			    checkcls = thePatterns[i].clsid;
				if($("#"+fid).is(".val-"+checkcls) && $("#"+fid).val() != "") 
					{
					var regExp = new RegExp(thePatterns[i].pattern,thePatterns[i].patternmod);
		            var correct = regExp.test(tval);
					if (!correct) 
						{
						$("#"+fid).addClass("errorEd");
						$("#errors").append('<label class="errorLabel" for="'+fid+'"><strong>'+tlabel+"</strong> "+thePatterns[i].errortxt+"</label>");
						++errorcount;
						} 
					else 
						{ 
						if(thePatterns[i].extrafunc) {
							thePatterns[i].extrafunc(fid);
						} 
						}
					}
				}  

			//  On 'text' types only, finally check for malicious input
			var malExp = new RegExp("SELECT.+FROM|GRANT.+ON.+TO|ALTER DATABASE|ALTER SCHEMA|ALTER TABLE|<[a-z]|<!|&#|\\Won[a-z]*\\s*=|(ascript\\s*:)|expression\\(","i");
			var malicious = malExp.test(tval);
			if(malicious) 
				{
				++errorcount;
				$("#"+fid).addClass("errorEd");
				$("#errors").append('<label class="errorLabel" for="'+fid+'"><strong>'+tlabel+"</strong>"+"Please modify and re-enter text."+"</label>");
				}
			}
			
	return errorcount;
	
	};
	
/* valAll runs when all fields need to be validated at once, rather than one at a time. */	
	function valAll(formid) 
		{
		var errct = 0;
	    $("#"+formid+" label.errorLabel").remove();
		$("#"+formid+" *").removeClass("errorEd");
		$("#"+formid+" input").add("#"+formid+" textarea").add("#"+formid+" select").each(function() 
			{
			errct = errct + (valField($(this).attr("id")));
			}
		);
		return errct;
		};

/* --- @event attachment and miscellaneous ------------------------------------ */

	$(".val-required").parents("label").addClass("reqField");
	
	$("input").add("textarea").add("select").blur(function() 
		{
		valField($(this).attr("id"));
		}
	);
	
	$("input").add("textarea").add("select").focus(function() 
		{
		$(this).removeClass("errorEd");
		var vid = $(this).attr("id");
		$("label.errorLabel[@for="+vid+"]").remove();
		}
	);	
	
	$("form").submit(function() 
		{
		var formid = $(this).attr("id");
		$("#errors").remove();
		$("#"+formid).prepend('<div id="errors" class="bottomBorder"></div>');
		$("#errors").hide();
		$("#errors").prepend('<h3 class="bottomBorder"><span>Errors Found</span></h3>');
		var errors = valAll(formid);
		if(errors > 0) 
			{
			$("#errors").show();
			return false;
			} 
		}
	);

});
/* *** ^END MAIN LOOP ********************************************************* */
