/*---------------------------------------
	BRIEF DOCUMENT STRUCTURE OVERVIEW
-----------------------------------------*/
/*	1. object initializers
	2. home made jquery plugins
*/

/*--------------------------------------------------------------------------
	WESEED'S TOP LEVEL OBJECT INITIALIZER NAMES (alphabetically ordered)
----------------------------------------------------------------------------*/
var Validation,
	Validating,
	ValidatingHelper;

/* VALIDATION OBJECT */
Validation = {
	"__errorDefaults": {
		errorMsgBesideLbl: false,  	  // put the error message beside the label if false then put the error message beside the input
		errorBorders:      true,  	  // put a border around the error input if true
		errorMsg:          "default", // the error message, required is the default
		errorPosX:         "right",	  // place the message to the right or left of the field (field being the input or the label)
		errorPosY:		   "",		  // place the message to the top or bottom of the field (field being the input or the label)
		errorPosXAdjust:   0,		  // give a number to this if you need to adjust the x coordinate value of the error message
		errorPosYAdjust:   0		  // give a number to this if you need to adjust the y coordinate value of the error message
	},
		
	'create': function(o) {
		$("form.validateForm, div.validateForm").createFormValidation(o);
	},
	
	'fromServer': function(o) {
			o 	 = o 	  || {};
		var	data = o.data || false,
			form = o.form || false;
		if (!form) { return false; }
		form.createFormValidation(data); // {"fieldErrors":{"title":"You have already created this song. Please try another."}}
	},
	
	"removeErrorMessages": function() {
		$("i.errorLbl").remove();
		$("i.frmFldError, input.frmFldError, textarea.frmFldError").removeClass("frmFldError");
		$("div.errorMsgsWrap").remove();
		this.removeFlag();
	},
	
	"removeFlag": function() {
		this.flag = false;
	},

	"main":function(o) {
		this.flag 		 		  = false;
		this.arrFields 	 		  = o.arrFields;
		this.arrValidationFields  = o.arrValidationFields || this.arrValidationFields;
		this.frm 		 		  = o.frm;
		this.submitBtn	 		  = o.submitBtn;
		this.frmOptions  		  = o.frmOptions;
		this.onchange			  = o.onchange || false;
		this.errorMsgsNoFadeOut	  = this.frmOptions.errorMsgsNoFadeOut || false;
		this.errorMsgsFadeOutTime = this.frmOptions.errorMsgsFadeOutTime || 3000;
		this.frmAjax	 		  = this.frmOptions.ajax || false;
		this.errorStyle2 		  = this.frmOptions.errorStyle2 || false;
		this.errorMsgsWrap 		  = $("#errorMsgsWrap_"+this.frm.attr("id")) || false;
		this.errorMsgsArray 	  = [];
		this.vFromServer 	  	  = (o.fromServer) ? o.fromServer : false;
		return this.validateForm();
	},
	
	"flagOccured": function(errorMsg) {
		this.flag 	  = true;
		this.errorMsg = errorMsg;
		if (this.errorStyle2) {
			this.createErrorLblsAndBorders2();
		} else {
			this.createErrorLblsAndBorders();
		}
	},

	"validateForm": function() {
		var frm 	  	 = this.frm,
			arrFields	 = this.arrFields,
			arrFieldsRev = arrFields.get().reverse(), // to give focus to the first inputs
			field,
			arrFrmValAtt,
			vResult,
			validatingMethod;
		$.each(arrFieldsRev, function(i, val) {
			field = $(this);
			Validation.field = field;
			if (field.is(":visible") && field.is(":enabled")) {
				arrFrmValAtt = field.attr("validation").split("_");
				for (var j in arrFrmValAtt) {
					if (arrFrmValAtt[j] == "fromServer" && Boolean(Validation.vFromServer)) { // for server side validation
						for (var k in Validation.vFromServer.serverFieldErrors) {
							if (field[0].name.toLowerCase() == k.toLowerCase()) {
								/* SERVER SIDE VALIDATION
								   field[0] = the element with fromServer validation attribute value
								   k 		= the field name that the server spit back
								   Validation.vFromServer.serverFieldErrors[k] = the error message from the server
								*/
								Validating.fromServer(Validation.vFromServer.serverFieldErrors[k]);
							}
						}
					} else { // for client side validation
						if (arrFrmValAtt[j] != "fromServer") {
							validatingMethod = Validating[arrFrmValAtt[j]];
							if (typeof validatingMethod != "undefined" && validatingMethod()) { // if it is a key and the return value is true
								break;
							}
						}
					}
				}
			}
		});
		
		// FOR ERROR STYLE 2
		if (this.errorStyle2) {
			$.each(this.arrValidationFields, function(i, val) {
				$(this).unbind("change.createFormValidation");
			});
			if (!this.flag) { this.errorMsgsWrap.remove(); }
			if ( this.flag) {
				this.insertErrorMsgs();
				this.errorField.unbind("change.createFormValidation");
				this.errorField.bind("change.createFormValidation", function() { // ADDING ON CHANGE EVENT
					var frm 	  	= Validation.frm,
						frmId	  	= frm.attr("id"),
						submitBtn 	= Validation.submitBtn,
						arrFields 	= $(this),						
						frmOptions  = frm.returnJsonObject("jsonValidationOptions"),
						json 	  	= {'frm':frm, 'arrFields':arrFields, 'submitBtn':submitBtn, 'frmOptions':frmOptions, 'onchange':true};
					Validation.main(json);
				});
			} else {
				this.insertErrorMsgs(false);
			}
		}
		
		// no flag so submit that sucka!
		if (!this.flag && !this.onchange) {
			this.removeErrorMessages();
			if (this.frmAjax) {
				return false;
			} else { /* submit the form if it isn't ajax. */
				this.frm.unbind("submit.createFormValidation");
				this.frm.submit();
			}
		} else {
			return false; // there was a flag so don't submit it.
		}
	},
	
	"insertErrorMsgs": function(insertErrorMsgs) {
			insertErrorMsgs 		= (typeof insertErrorMsgs == "undefined") ? true : false;
		var frm 	  				= this.frm,
			field	  				= this.field,
			frmId    	 			= frm.attr("id"),
			errorMsgsWrap 			= this.errorMsgsWrap,
			errorMsgsArrayFormatted = this.errorMsgsArray.reverse().toString().replace(/<\/li>,/g, "</li>"); // I don't remember why I'm doing a replace?
			
		if (insertErrorMsgs) {
			if (!errorMsgsWrap.length) {
				this.createErrorMsgWrap(frmId);
				errorMsgsWrap = this.errorMsgsWrap;
			}
			this.populateErrorMsgWrap(errorMsgsWrap, errorMsgsArrayFormatted);
		} else {
			this.errorMsgsArray = [];
			errorMsgsWrap.fadeOut(function() {
				$(this).find("ul").html("");
				$(this).remove();
			});
		}
	},
	
	"populateErrorMsgWrap": function(errorMsgsWrap, errorMsgsArrayFormatted, submitButton, errorMsgsFadeOutTime) {
		submitButton 		 = arguments[2] || false;
		errorMsgsFadeOutTime = arguments[3] || this.errorMsgsFadeOutTime;
		
		
		errorMsgsWrap.find("ul:first").html(errorMsgsArrayFormatted);
		
		if (!Boolean(submitButton)) {
			this.errorMsgY = this.errorMsgY - errorMsgsWrap.height();
		} else {
			this.errorMsgX = submitButton.offset().left - 75;
			this.errorMsgY = submitButton.offset().top - submitButton.height() - errorMsgsWrap.height() - 25;
		}
		
		errorMsgsWrap.css({'top':this.errorMsgY, 'left':this.errorMsgX});
		if (!this.errorMsgsNoFadeOut) {
			TO = setTimeout( function() {
				errorMsgsWrap.fadeOut(function() {
					$(this).find("ul").html("");
					$(this).remove();
				});
			}, errorMsgsFadeOutTime);
		}
	},
	
	"createErrorMsgWrap": function(frmId) {
		$("body").append('<div class="errorMsgsWrap" id="errorMsgsWrap_'+frmId+'">' 							+
							 '<a href="javascript:;" class="errorMsgClose" title="close error message">X</a>' 	+
							 '<ul></ul>' 																		+
						 '</div>');
		this.errorMsgsWrap = $("#errorMsgsWrap_"+frmId);
		var errorMsgsWrap  = this.errorMsgsWrap;
		errorMsgsWrap.fadeIn();
		errorMsgsWrap.find("a.errorMsgClose").click(function() {
			$(this).parent().fadeOut(function() {
				var me = $(this);
				me.find("ul").html("");
				me.remove();
			});
		});
	},
	
	"createErrorLblsAndBorders2": function() { // FOR ERROR STYLE 2
		var field	 	 	= this.field,
			fieldId  	 	= field.attr("id") || "noLblId",
			lbl      	 	= $("label[for='"+fieldId+"']"),
			frmOptions 	 	= this.frmOptions,
			fieldOptions 	= field.returnJsonObject("jsonValidationOptions"),
			yPos  	 	 	= (field.attr("type") == "checkbox") ? 25 : 16,
			yAdj		 	= frmOptions.errorPosYAdjust 	|| 0,			
			xAdj		 	= frmOptions.errorPosXAdjust 	|| 0,			
			lblTextOffForm  = frmOptions.lblTextOff   || false,
			lblTextOffField = fieldOptions.lblTextOff || false,
			lblTextOnField  = (lblTextOffForm && fieldOptions.lblTextOn) ? true : false,
			lblText, errorMsg;
			
			yAdj = fieldOptions.errorPosYAdjust  || yAdj;
			xAdj = fieldOptions.errorPosXAdjust  || xAdj;
		
		if (!lblTextOffForm && !lblTextOffField || lblTextOnField) {
			lblText	= fieldOptions.errorMsg || lblText;
			lblText	= $.trim(lbl.text().replace("*",""));
			lblText = (lblText.charAt(lblText.length-1) == ":" || lblText.length === 0) ? lblText : lblText + ":";
			lblText = lblText+" "+this.errorMsg;
		} else {
			lblText = this.errorMsg;
		}
		
		errorMsg = fieldOptions.errorMsg || lblText;
		
		//this.errorMsgsArray.push("<li>"+errorMsg+"</li>"); 	// for all error messages in the errorMsgsWrap div
		this.errorMsgsArray[0] = "<li>"+errorMsg+"</li>"; 		// for one error message in the errorMsgsWrap div
		this.errorMsgY  = field.offset().top - 27 - yPos + yAdj;
		this.errorMsgX  = field.offset().left - 15 + xAdj;
		this.errorField = field;
		field.focus().select();
	},

	"createErrorLblsAndBorders": function() {
		var frm 			= this.frm,
			field			= this.field,
			frmId       	= frm.attr("id"),
			fieldId     	= field.attr("id"),
			frmAndFieldId 	= frmId+fieldId,
			lbl         	= $("label[for='"+fieldId+"']"),
			o				= {},
			frmOptions		= this.frmOptions,
			fieldOptions	= field.returnJsonObject("jsonValidationOptions"),
			error,
			xWidth,
			xHeight,
			errorWidth,
			xPos,
			yPos,
			errorMsgBesideLbl,
			errorMsg,
			isCheckbox, isSelect, is4Sides, css4sides, cbW, cbH, cbX, cbY, brd;

		/* custom options
		   to override the default you would give the html element an attribute of options and set it equal to the json object
		   e.g., {errorMsgBesideLbl:false, errorBorders:false, errorMsg:'error message', errorPosX:'left', errorPosY:'top', errorPosXAdjust:20, errorPosYAdjust:20}
		*/
		// build main options before element iteration
		$.extend(o, this.__errorDefaults, frmOptions, fieldOptions);

		field.focus().select();

		errorMsgBesideLbl = (!lbl.attr('for')) ? false : o.errorMsgBesideLbl;		// if there is no for attribute then don't show the error message label
		errorMsg 		  = (o.errorMsg == "default") ? this.errorMsg : o.errorMsg;	// if there is jsonValidationOptions="{errorMsg:'custom message'}" then overide the default error message 
		
		if (o.errorBorders) { // if you want borders around the input where the error occurred
			isCheckbox = (field.attr("type") == "checkbox") 		  ? true: false;
			isSelect   = (field[0].tagName.toLowerCase() == "select") ? true: false;
			if (isCheckbox || isSelect) {
				for (var j=1; j < 5; j++) {
					is4Sides = ($("i#"+frmAndFieldId+"_"+j).length) ? true : false;
					if (is4Sides) { break; }
				}
				if (!is4Sides) { // if element doesn't exists
					cbW = 0;
					cbH = (isCheckbox) ? field.innerHeight() - 2 : field.innerHeight();
					cbX = field.offset().left;
					cbY = field.offset().top;
					brd = "left";
					this.createBorderSide(cbW, cbH, cbX, cbY, brd, frmAndFieldId, 1); // LEFT
					
					cbH = (isCheckbox) ? field.innerHeight() - 2 : field.innerHeight();
					cbX = (isCheckbox) ? (field.offset().left + field.innerWidth()) - 1 : (field.offset().left + field.innerWidth()) + 1;
					this.createBorderSide(cbW, cbH, cbX, cbY, brd, frmAndFieldId, 2); // RIGHT
					
					cbH = 0;
					cbW = (isCheckbox) ? field.innerWidth() - 2 : field.innerWidth();
					cbX = field.offset().left;
					brd = "top";
					this.createBorderSide(cbW, cbH, cbX, cbY, brd, frmAndFieldId, 3); // TOP
					
					cbY = (isCheckbox) ? (field.offset().top + field.innerHeight()) - 1 : (field.offset().top + field.innerHeight()) + 1;
					this.createBorderSide(cbW, cbH, cbX, cbY, brd, frmAndFieldId, 4); // BOTTOM
				}
			} else {
				field.addClass("frmFldError");
			}
		}

		if (!$("#"+frmAndFieldId).length) { // if label doesn't already exist
			if (errorMsg) {
				$("body").append('<i class="errorLbl" id="'+frmAndFieldId+'">'+errorMsg+'</i>');
				error  	   = $("i#"+frmAndFieldId);
				errorWidth = error.width();
			} else {
				errorWidth = 0;
			}
			if (errorMsgBesideLbl) {
				switch (o.errorPosX) {
					case "right":
						xWidth = lbl.width() + 5;
						errorWidth = 0;
						break;
					case "left":
						xWidth = -($("#"+frmAndFieldId).width() + 2);
						break;
					default:
						xWidth = 0;
						// do nothing
				}				
				switch (o.errorPosY) {
					case "top":
						xHeight = -(lbl.height() - 8);
						break;
					case "bottom":
						xHeight = lbl.height() + 2;
						break;
					default:
						xHeight = 3;
						// do nothing
				}
				xPos = (lbl.offset().left + xWidth)  + (o.errorPosXAdjust + errorWidth) || lbl.offset().left + xWidth;
				yPos = (lbl.offset().top  + xHeight) + o.errorPosYAdjust 			    || lbl.offset().top  + xHeight;

			} else {
				switch (o.errorPosX) {
					case "right":
						xWidth = field.width() + 18;
						errorWidth = 0;
						break;
					case "left":
						xWidth = -($("#"+frmAndFieldId+"").width() + 5);
						break;
					default:
						xWidth = 0;
						// do nothing
				}				
				switch (o.errorPosY) {
					case "top":
						xHeight = -(20);
						break;
					case "bottom":
						xHeight = field.height() + 10;
						break;
					default:
						xHeight = 0;
						// do nothing
				}
				xPos = (field.offset().left + xWidth)  + (o.errorPosXAdjust + errorWidth) || field.offset().left + xWidth;
				yPos = (field.offset().top  + xHeight) + o.errorPosYAdjust 				  || field.offset().top  + xHeight;
			}

			// result
			$("#"+frmAndFieldId).css({ left:xPos, top:yPos });
		}
	},
	
	"createBorderSide": function(cbW, cbH, cbX, cbY, brd, frmAndFieldId, j) {
		var css4sides = 'width:'+cbW+'px; height:'+cbH+'px; left:'+cbX+'px; top:'+cbY+'px; border-'+brd+':none !important';
		$("body").append('<i class="errorLbl frmFldError" id="'+frmAndFieldId+'_'+j+'" style="'+css4sides+'"></i>');
	}
};

Validation.removeFlag(); // initialize the flag, set it to false;


/* VALIDATING OBJECT - IMPORTANT required method needs to be first then the rest of the methods are in alphabetical order */
Validating = {
	"required": function() {
		var field = Validation.field;
		if ($.trim(field.val()) === "") {
			Validation.flagOccured('required');
			return true;
		}
	},
	"alphanumeric": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var reg = new RegExp(/^[0-9a-zA-Z]+$/);
		if (!reg.test(field.val())) {
			Validation.flagOccured('only numbers and letters');
			return true;
		}
	},
	"alphanumericAndSpaces": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var reg = new RegExp(/^[0-9a-zA-Z ]+$/);
		if (!reg.test(field.val())) {
			Validation.flagOccured('only numbers, letters and spaces');
			return true;
		}
	},
	"checked": function() {
		var field = Validation.field;
		if (field[0].checked === false) {			
			Validation.flagOccured('required');
			return true;
		}
	},
	"currency": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var vChars = "0123456789.,$";
		for (var i = 0; i < field.val().length; i++) {
			if (vChars.indexOf(field.val().charAt(i)) == -1) {
				Validation.flagOccured('only numbers (0-9), dollar sign, commas and a period are allowed');
				return true;
			}
		}
	},
	"email": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var reg = new RegExp(/^(("[\w\-\s]+")|([\w\-\+]+(?:\.[\w\-]+)*)|("[\w\-\s]+")([\w\-]+(?:\.[\w\-]+)*))(@((?:[\w\-]+\.)*\w[\w\-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$)|(@\[?((25[0-5]\.|2[0-4][0-9]\.|1[0-9]{2}\.|[0-9]{1,2}\.))((25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\.){2}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[0-9]{1,2})\]?$)/i);
		if (!reg.test(field.val())) {
			Validation.flagOccured('invalid email');
			return true;
		}
	},
	"length": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var pLength 	= 10,
			o 			= field.returnJsonObject("jsonValidationOptions"),
			charLength 	= o.charLength || false;
		pLength = (charLength) ? charLength : pLength; // if you need the max characters to be higher then 10 then specify it on the element like this jsonValidationOptions="{'charLength':20}"
		if (field.val().length < pLength) {
			Validation.flagOccured('at least '+pLength+' characters required');
			return true;
		}
	},
	"match": function() {
		var field 	  = Validation.field,
			arrFields = Validation.arrValidationFields,
			testConfirmField;
		if (field.val() === "") { return false; }
		
		var arrValidationAttr = field.attr("validation").split("_"),
			matchX,
			arrConfirms = [];
			
		$.each(arrValidationAttr, function(i, val) {
			if (val == "match") {
				matchX = arrValidationAttr[i + 1]; // example match1
			}
		});

		$.each(arrFields, function(i, val) {
			arrValidationAttr = $(this).attr("validation").split("_");
			for (var j in arrValidationAttr) {
				if (arrValidationAttr[j] == matchX) {
					arrConfirms.push($(this));
				}
			}
		});
		
		testConfirmField = Validation.field.attr("id") == arrConfirms[1].attr("id");
		testConfirmField = (arrConfirms[1].attr("validation").indexOf("required") == -1) ? true : testConfirmField;
		
		if (arrConfirms[0].val() != arrConfirms[1].val() && testConfirmField) {
			Validation.field = arrConfirms[1]; // special case.
			Validation.flagOccured('your fields did not match');
			return true;
		}
	},
	"maxLength": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var pLength 	= 30,
			o 			= field.returnJsonObject("jsonValidationOptions"),
			maxLength 	= o.maxLength || false;
		pLength = (maxLength) ? maxLength : pLength; // if you need a custom maxLength jsonValidationOptions="{'maxLength':20}"
		if (field.val().length > pLength) {
			Validation.flagOccured('only '+pLength+' characters allowed');
			return true;
		}
	},
	"maxNumber": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
 		
 		var	o 	= field.returnJsonObject("jsonValidationOptions"),
			max = o.maxNumber || 10000000;
 		
		if (!(ValidatingHelper.stripCommas(field.val()) <= max)) {
			Validation.flagOccured(ValidatingHelper.addCommas(max)+' is the maximum amount');
			return true;
		}
	},
	"minimumAge": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		
		var o 			= field.returnJsonObject("jsonValidationOptions"),
			minimumAge 	= o.minimumAge || 13, 		// Children's Online Privacy Protection Act, must be 13 or over
			now 		= new Date(),
	      	fldDate  	= field.val().split('-'), 	// format must be: yyyy-mm-dd
	      	born, years, base, myAge, frmAndMeId;
	      
		if (fldDate.length == 3) { // if it is a date.
			born  = new Date(fldDate[0], fldDate[1]*1-1, fldDate[2]);
			years = new Date(now.getTime() - born.getTime());
			base  = new Date(0);
			myAge = (years.getFullYear()-1) - base.getFullYear();
			if (myAge < minimumAge) {
				Validation.flagOccured('you must be '+minimumAge+' or over');
				return true;
			}
		}
	},
	"number": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		if (isNaN(ValidatingHelper.stripCommas(field.val()))) { // isNaN() checks if a value is not a number
			Validation.flagOccured('only numeric values');
			return true;
		}
	},
	"phone": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; } 					// If it is blank that is ok if it is required, the required() will pick it up.
		var digits = "0123456789",									// Declaring required variables
			phoneNumberDelimiters = "()-. ",						// non-digit characters which are allowed in phone numbers
			validWorldPhoneChars = phoneNumberDelimiters + "+",		// characters which are allowed in international phone numbers (a leading + is OK)
			minDigitsInIPhoneNumber = 10;							// Minimum no of digits in an international phone no.

		function isInteger(s) {   
			for (var i = 0; i < s.length; i++) {   
				// Check that current character is number.
				var c = s.charAt(i);
				if (((c < "0") || (c > "9"))) { return true; }
			}
			// All characters are numbers.
			return true;
		}

		function stripCharsInBag(s, bag) {
			var returnString = "";
			// Search through string's characters one by one. If character is not in bag, append to returnString.
			for (var i = 0; i < s.length; i++) {   
				// Check that current character isn't whitespace.
				var c = s.charAt(i);
				if (bag.indexOf(c) == -1) { returnString += c; }
			}
			return returnString;
		}

		function checkInternationalPhone(strPhone) {
			s = stripCharsInBag(strPhone,validWorldPhoneChars);
			return (isInteger(s) && s.length >= minDigitsInIPhoneNumber);
		}

		if (checkInternationalPhone(field.val())===false) {
			Validation.flagOccured('invalid phone');
			return true;
		}
	},
	"positiveWholeNumber": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		//var regNeg = new RegExp(/^\s*(\+|-)?\d+\s*$/); 	// allow negative numbers
 		var regPos = new RegExp(/^[1-9]+[0-9]*$/);			// no negative numbers or zero
		if (!regPos.test(ValidatingHelper.stripCommas(field.val()))) {
			Validation.flagOccured('only positive whole numbers');
			return true;
		}
	},
	"selected": function() {
		var field = Validation.field;
		if (field[0].selectedIndex === 0) {
			Validation.flagOccured('must select one');
			return true;
		}
	},
	"zip": function() {
		var field = Validation.field;
		if (field.val() === "") { return false; }
		var temp,
			valid 		= "0123456789-",
			hyphencount = 0,
			vFldValue 	= field.val();

		if (vFldValue.length != 5 && vFldValue.length != 10) {
			Validation.flagOccured('invalid zip code. 5 digit or 5 digit + 4. e.g., 12345 or 12345-6789');
			return true;
		}	
		for (var i=0; i < vFldValue.length; i++) {
			temp = "" + vFldValue.substring(i, i+1);
			if (temp == "-") {
				hyphencount++;
			}
			if (valid.indexOf(temp) == "-1") {
				Validation.flagOccured('invalid characters in your zip code');
				return true;
			}
			if ((hyphencount > 1) || ((vFldValue.length==10) && "" + vFldValue.charAt(5) != "-")) {
				Validation.flagOccured('hyphen is needed with extended zip code.  e.g., 12345-6789');
				return true;
			}
		}
	},	
	"fromServer": function(msg) {
		var field = Validation.field;
		Validation.flagOccured(msg);
		return true;
	}
};

/* VALIDATING HELPER OBJECT - (useful functions that involve text or numbers) */
ValidatingHelper = {
	"addCommas": function(nStr) {
		nStr 	+= '';
		var x    = nStr.split('.'),
			x1   = x[0],
			x2   = x.length > 1 ? '.' + x[1] : '',
			rgx  = /(\d+)(\d{3})/;
		while (rgx.test(x1)) {
			x1 = x1.replace(rgx, '$1' + ',' + '$2');
		}
		return x1 + x2;
	},
	"stripCommas":function(str) {
	    var re = /,/g;
	    return str ? str.replace(re,"") : str;
	}
};

/*---------------------
	FORM VALIDATION
-----------------------*/
(function($){
	// BIND INTO STACK
	$.extend($.fn, {
		// Determine the objects position in the cache, if any
		cachePosition: function(){
			return $.data(this[0]);
		},
		
		// Allow the user to splice an event into a specific position in the event cache 
		bindIntoStack: function(pos, type, func){
			return this.each(function(){
				var namespaces = type.split("."),	// Explode out namespaces, if any
					evType = namespaces.shift(),	// Grab the actual type
					el = $(this),					// Use the jQuery reference to the first element instead of the domElement
					position = el.cachePosition();	// Get the location of the element in the cache.
				
				if (!position) { return; }			// If we have a position, we can access the cache, which holds the jQuery event stack.
					
				var cache = $.cache[ position ];	// Grab a reference to the cache so we can do some sanity checks

				el.bind(type, func);				// Now we need to bind the new function to the call stack through jQuery

				if (!cache ||						// If we don't have the a pre-existing event cache, bind the function as a new entry and exit
					!cache.events || 
					!cache.events[ evType ]) { return;	}
					
				var events = cache.events[ evType ],// Grab a copy of the events cache for the given type
					fromExpando = [],				// [stackPosition => uuidIDInjQueryCache, ...]
					toReplace = null,				// A copy of the function we'll be replacing, if any
					i = 0;
					
				$.each(events, function(k){
					fromExpando.push(k);
					if (i == pos) { toReplace = this; } // If the positions are equal, this is the function we want to replace
					++i;
				});
				
				if (!toReplace) { return; }	 		// If the position in the stack has not yet been reached, there's no slicing to be done

				var newPos = fromExpando.pop();		// The position we'll be placing in
				
				// Perform the actual position swap in the cache
				if (typeof $.cache[ position ].events[ evType ][ fromExpando[pos] ] != "undefined") {
					$.cache[ position ].events[ evType ][ fromExpando[pos] ] = $.cache[ position ].events[ evType ][ newPos ];
				}
				$.cache[ position ].events[ evType ][ newPos ] = toReplace;
			});
			
			// return this; // you can't get here
		}
	});
	
	/* return eval-ed content of an element's attribute (needs to be a js object in the attribute) */
	$.fn.returnJsonObject = function(htmlAttr) {
		if(!this || !this[0]){return false;}
		var meAttr = $(this).attr(htmlAttr);
		return (meAttr) ? eval('(' + meAttr + ')') : false;
	};
	
	$.fn.createFormValidation = function(o) {
		if(!this || !this[0]){return false;}
			o		   		  = o || {};
		var	serverError		  = o.errorMessages || false, // server error
			serverFieldErrors = o.fieldErrors   || false; // for server side validation
		
		$(this).each(function(i, val) {
			var frm 	  	 = $(this),
				frmId	  	 = frm.attr("id"),
				submitBtn 	 = o.submitButton || frm.find(".submitButton"),
				arrFields 	 = frm.find("input[validation], textarea[validation], select[validation]"),
				frmOptions   = frm.returnJsonObject("jsonValidationOptions"),
				json 	  	 = {'frm':frm, 'arrFields':arrFields, 'arrValidationFields':arrFields, 'submitBtn':submitBtn, 'frmOptions':frmOptions},
				errorStyle2  = frmOptions.errorStyle2  || false,
				noKeyupEvent = frmOptions.noKeyupEvent || false;

			if (Boolean(serverError)) { // server error messages
				var errorMsgsArray = [],
					errorMsgsArrayFormatted;
				for (var j in serverError) {
					if (serverError.hasOwnProperty(j)) {
						errorMsgsArray.push("<li>"+serverError[j]+"</li>");
					}
				}
				Validation.createErrorMsgWrap("serverError");
				errorMsgsArrayFormatted = errorMsgsArray.toString().replace(/<\/li>,/g, "</li>"); // I don't remember why I'm doing a replace?
				Validation.populateErrorMsgWrap(Validation.errorMsgsWrap, errorMsgsArrayFormatted, submitBtn, 4500);
				return false;
			}
			
			if (Boolean(serverFieldErrors)) { // for server side validation
				fromServer = {'fromServer':{'serverFieldErrors':serverFieldErrors}};
				$.extend(json, fromServer);
				return Validation.main(json);
			}
			
			if (typeof submitBtn[0] == "undefined") { return false; } // no submit button so don't bind. 
			
			if (frm[0].tagName.toLowerCase() == "form") {	// IF $(this) IS A FORM
				frm.unbind("submit.createFormValidation");
				frm.bindIntoStack(0, "submit.createFormValidation", function(){
					return Validation.main(json);
				});
			}
			
			submitBtn.unbind("mouseup.createFormValidation");
			submitBtn.bind("mouseup.createFormValidation", function(){
				/*  * if Validation.flag == false, it returns true and submits the form.
					* if Validation.flag == true, it returns false and doesn't submit the form.
					* if Validation.flag == false && Validation.frmAjax == true, it returns false
					      and doesn't submit the form. Meaning that, there were no errors but
					      Validation.frmOptions.ajax was set to true in the jsonValidationOptions
					      form tag attribute. Then in some other js method the ajax call is made
					      right after the code if (Validation.flag) { return false }
					      which is located in the same method.
				*/
				Validation.main(json);
			});
			
			if (!noKeyupEvent) {
				frm.unbind("keyup.createFormValidation");
				frm.bind("keyup.createFormValidation", function(e) {
					if(e.keyCode == 13 && e.target.tagName.toLowerCase() == "input") {
						Validation.main(json);
					}
				});
			}
			
			// Validate Form Attach On Change To Flds
			if (!errorStyle2) {
				$.each(arrFields, function(i, val) {
					var field 	  = $(this),
						fieldJson = field.returnJsonObject("jsonValidationOptions"),
						me, meId, frmAndMeId;
					
					field.unbind("change.createFormValidation");
					field.bind("change.createFormValidation", function() {
						me 			= $(this);
						meId 		= me.attr("id");
						frmAndMeId  = frmId + meId;
						me.removeClass("frmFldError");
						$("i#"+frmAndMeId).remove();
						for (var j=1; j < 5; j++) {
							$("i#"+frmAndMeId+"_"+j).remove();
						}
						
						// not used very often, only when you set another fields value and then have to force the change event to happen on that field.
						if (fieldJson && fieldJson.fireChangeOnField) {
							frm.find("[name='"+fieldJson.fireChangeOnField+"']").trigger('change.createFormValidation');
						}
					});
				});
			}
		});
	};
})(jQuery);