/*==============================================
* Form Validator v2.0
* Copyright (c)2004 - 2006 Vtrenz, Inc.
*
* Date: 08/13/2004
* Written By: JD Hendrickson & Rob Johnson
*
* Last Updated:
*	12/18/2005 Rob Johnson
*		Added a hack to overcome prototype.js naming convention (object.prototype.fields)
*
*	01/17/2006 Rob Johnson
*		Added an option to use a custom error message when invalid form is submitted
==============================================*/

/*==============================================
FormValidator Constructor
==============================================*/
function formValidator(){
	// Properties
	// =========================================
	var forms = new Array();			// collection of form objects.
	var self = this;					// pointer to the 'this' scope.
	
	// Public Method Pointers
	// =========================================
	this.addForm = addForm;
	
	// Methods
	// =========================================
	function addForm(pFormName){
		if(checkExistance(pFormName)) {
			eval('this.' + pFormName + ' = new VTRENZForm(\'' + pFormName + '\');');
			return true;
		} else {
			alert('The form \"' + pFormName + '\" could not be found.');
			return false;
		}
	}
	function checkExistance(pFormName){
		if( document.forms[pFormName])
			return true;
		else 
			return false;
	}
	function getDOM(){
		var oDOM = document;		
		if(oDOM.all) oDOM = oDOM.all;
		else if(oDOM.getElementById) oDOM = oDOM.getElementById;		
		return(oDOM);
	}
}
//==============================================

/*==============================================
Form Constructor
==============================================*/
function VTRENZForm(pFormName){
	var name = pFormName;
	var self = this;
	this.fields = new Array();
	this.fieldCount = 0;
	
	//THIS IS A HACK TO OVERCOME THE PROTOTYPE.JS POOR NAMING CONVENTION
	for(var i in this.fields) delete this.fields[i];

	// Public Method Pointers
	//==========================================
	this.getName = getName;
	this.setName = setName;
	this.validate = validate;
	this.addField = addField;
	
	// Methods
	//==============================================
	function getName(){return(name);}
	function setName(pName){name = pName;}
	function validate(){
		var isValid = true;

		for(var currentField in this.fields){
			if(this.fields[currentField].validate && !this.fields[currentField].validate()){
				isValid = false;
				alert(this.fields[currentField].getErrorMessage());
				
				if(getDOM()[this.fields[currentField].getName()].type != 'hidden' && !getDOM()[this.fields[currentField].getName()].disabled) {
					if(getDOM()[this.fields[currentField].getName()].focus)
						getDOM()[this.fields[currentField].getName()].focus();
					if(getDOM()[this.fields[currentField].getName()].select)
						getDOM()[this.fields[currentField].getName()].select();
				}
					
				break;
			}
		}		
		return(isValid);
	}
	function addField(pFieldName){
		if(checkExistance(pFieldName)) {
			this.fields[pFieldName] = new VTRENZField(pFieldName, name);
			fieldCount = countFields();

			return true;
		} else {
			alert('A field named \"' + pFieldName + '\" could not be found in the form: \"' + name + '\".');
			return false;
		}
	}
	function checkExistance(pFieldName){
		if( document.forms[name].elements[pFieldName] )
			return true;
		else 
			return false;
	}
	function getDOM(){
		var oDOM = document.forms[name].elements;		
		return(oDOM);
	}
	function countFields(){
		var cnt = 0;
		for(var i in self.fields) cnt++;
		return(cnt);
	}
}







//============================================================
/*************************************************************
	Object:		Field
	Author:		Rob Johnson
	Email:		rjohnson@vtrenz.com
	Created:	Aug. 12, 2004
		
	Brief Desc:	
		The Field object represents a field in an html form.  
		The Field object can be used to define a field's data 
		type, format, and requirements.
*************************************************************/
//============================================================
//Constructor
function VTRENZField (sName, sForm) {
	//private properties
	var self = this;
	var name;
	var dataType = "string";
	var form;
	var displayName;
	var allowNull = new Boolean(0);
	var allowMarkup = new Boolean(1);
	var minLength = 0;
	var maxLength = 0;
	var minSelect = 0;
	var maxSelect = 0;
	var cardType = "visa";
	var dateRange = {};
	var timeRange = {};
	var errors = new Array();
		errors.code = 0;
		errors.message = "";
	var customErrorMessage = "";

	/******************************************************
	****************** Privileged Methods *****************
	******************************************************/
	
	/***************** Initialize Methods ****************/
	this.init = function (sName, sForm) {
		//set name
		if(sName) {
			this.setName(sName);
			this.setDisplayName(sName);
		}
		//set form
		if(sForm)
			this.setForm(sForm);
	}
	//////////////////////////////


	/***************** Validate Methods ****************/
	this.validate = function () {
		//get form DOM handler
		var oDOMHandler = getDOMElement(this.getForm());
		
		if(oDOMHandler) {
			//get field's type
			var sElementType = getType(oDOMHandler);

			//get field's current value
			var sElementValue = trim(getValue(oDOMHandler));

			//validate allow null (required value)
			if(!this.getAllowNull() && isNull(sElementValue)) {
				this.setErrors(0, 'The field "'+this.getDisplayName()+'" is a required field.');
				return false;
			}
			
			//validate allow markup (html,xml,cfmx etc. <Tags>)
			if(!this.getAllowMarkup() && isMarkup(sElementValue)) {
				this.setErrors(0, 'The field "'+this.getDisplayName()+'" does not allow markup such as <html>, <xml> or <cfmx>.');
				return false;
			}
			
			if(!isNull(sElementValue)) {//validate data type
				var dataTypeCheck = isDataValid(sElementValue);
				if(!dataTypeCheck[0]) {
					if(self.getCustomErrorMessage() != '')
						this.setErrors(0, self.getCustomErrorMessage());
					else
						this.setErrors(0, 'The field "'+this.getDisplayName()+'" expects a value of type: '+dataTypeCheck[1]+'.\n\nPlease be sure to enter a proper value.');
					return false;
				}
			}
			
			//validate min length
			if(sElementValue.length < this.getMinLength()) {
				this.setErrors(0, 'The field "'+this.getDisplayName()+'" must be at least '+this.getMinLength()+' character(s).');
				return false;
			}

			//validate max length
			if(this.getMaxLength() > 0 && sElementValue.length > this.getMaxLength()) {
				this.setErrors(0, 'The field "'+this.getDisplayName()+'" cannot be more than '+this.getMaxLength()+' character(s).');
				return false;
			}

			if(/^(radio|checkbox|select|select-multiple)$/.test(sElementType)) {
				//validate min select
				if(countSelections(oDOMHandler) < this.getMinSelect()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" requires at least '+this.getMinSelect()+' selection(s).');
					return false;
				}
	
				//validate max select
				if(this.getMaxSelect() > 0 && countSelections(oDOMHandler) > this.getMaxSelect()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" requires no more than '+this.getMaxSelect()+' selection(s) be made.');
					return false;
				}
			}
			
			if(/^(date)$/.test(dataType)) {
				//greaterThan
				if(dateRange.greaterThan != '' && new Date(sElementValue).valueOf() <= new Date(dateRange.greaterThan).valueOf()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" must be after '+dateRange.greaterThan+'.');
					return false;
				}
	
				//lessThan
				if(dateRange.lessThan != '' && new Date(sElementValue).valueOf() >= new Date(dateRange.lessThan).valueOf()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" must be before '+dateRange.lessThan+'.');
					return false;
				}
			}
			
			if(/^(time)$/.test(dataType)) {
				//greaterThan
				if(timeRange.greaterThan != '' && new Date('1/1/1990 '+sElementValue).valueOf() <= new Date('1/1/1990 '+timeRange.greaterThan).valueOf()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" must be after '+timeRange.greaterThan+'.');
					return false;
				}
	
				//lessThan
				if(timeRange.lessThan != '' && new Date('1/1/1990 '+sElementValue).valueOf() >= new Date('1/1/1990 '+timeRange.lessThan).valueOf()) {
					this.setErrors(0, 'The field "'+this.getDisplayName()+'" must be before '+timeRange.lessThan+'.');
					return false;
				}
			}
		} else {
			this.setErrors(0, 'Form cannot be found.');
			return false;
		}
		
		return true;
	}
	//////////////////////////////


	/***************** Getter/Setter Methods ****************/
	this.getName = function () {
		return name;
	}
	this.setName = function (sName) {
		var matchPattern = /^\w[^\s]*$/;
		if(matchPattern.test(sName))
			name = new String(sName);
		else
			return false;
	}
	//////////////////////////////
	this.getName = function () {
		return name;
	}
	this.setName = function (sName) {
		var matchPattern = /^\w[^\s]*$/;
		if(matchPattern.test(sName))
			name = new String(sName);
		else
			return false;
	}
	//////////////////////////////
	this.getDataType = function () {
		return dataType;
	}
	this.setDataType = function (sDataType) {
		var matchPattern = /^(string|int|number|money|date|time|email|creditcard|usphone|uszip|regex)$/;
		sDataType = sDataType.toLowerCase();
		if(matchPattern.test(sDataType))
			dataType = new String(sDataType);
		else
			return false;
	}
	//////////////////////////////
	this.getCardType = function () {
		return cardType;
	}
	this.setCardType = function (sCardType) {
		var matchPattern = /^(mastercard|visa|amex)$/;
		sCardType = sCardType.toLowerCase();
		if(matchPattern.test(sCardType))
			cardType = new String(sCardType);
		else
			return false;
	}
	//////////////////////////////
	this.getPattern = function () {
		return pattern;
	}
	this.setPattern = function (sPattern,sOptions) {
		if(!sOptions) sOptions='i';
		pattern = new RegExp(sPattern,sOptions);
	}
	//////////////////////////////
	this.getForm = function () {
		return form;
	}
	this.setForm = function (sForm) {
		var matchPattern = /^\w[^\s]*$/;
		if(matchPattern.test(sForm))
			form = new String(sForm);
		else
			return false;
	}
	//////////////////////////////
	this.getDisplayName = function () {
		return displayName;
	}
	this.setDisplayName = function (sDisplayName) {
		if(typeof(sDisplayName) == 'string')
			displayName = new String(sDisplayName);
		else
			return false;
	}
	//////////////////////////////
	this.getAllowNull = function () {
			return allowNull.valueOf();
	}
	this.setAllowNull = function (bAllowNull) {
		if(bAllowNull)
			allowNull = new Boolean(1);
		else
			allowNull = new Boolean(0);
	}
	//////////////////////////////
	this.getMinLength = function () {
		return minLength;
	}
	this.setMinLength = function (iMinLength) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMinLength))
			minLength = iMinLength;
		else
			return false;
	}
	//////////////////////////////
	this.getMaxLength = function () {
		return maxLength;
	}
	this.setMaxLength = function (iMaxLength) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMaxLength))
			maxLength = iMaxLength;
		else
			return false;
	}
	//////////////////////////////
	this.getLengthRange = function () {
		return new Array(minLength, maxLength);
	}
	this.setLengthRange = function (iMinLength, iMaxLength) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMinLength) && matchPattern.test(iMaxLength)) {
			minLength = iMinLength;
			maxLength = iMaxLength;
		} else {
			return false;
		}
	}
	//////////////////////////////
	this.getMinSelect = function () {
		return minSelect;
	}
	this.setMinSelect = function (iMinSelect) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMinSelect))
			minSelect = iMinSelect;
		else
			return false;
	}
	//////////////////////////////
	this.getMaxSelect = function () {
		return maxSelect;

	}
	this.setMaxSelect = function (iMaxSelect) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMaxSelect))
			maxSelect = iMaxSelect;
		else
			return false;
	}
	//////////////////////////////
	this.getSelectRange = function () {
		return new Array(minSelect, maxSelect);
	}
	this.setSelectRange = function (iMinSelect, iMaxSelect) {
		var matchPattern = /^[0-9]+$/;
		if(matchPattern.test(iMinSelect) && matchPattern.test(iMaxSelect)) {
			minSelect = iMinSelect;
			maxSelect = iMaxSelect;
		} else {
			return false;
		}
	}
	//////////////////////////////
	this.getDateRange = function () {
		return dateRange;
	}
	this.setDateRange = function (dGreaterThan,dLessThan) {
		if(!dGreaterThan) dGreaterThan='';
		if(!dLessThan) dLessThan='';

		if((dGreaterThan=='' || isDate(dGreaterThan)) && (dLessThan=='' || isDate(dLessThan)))
			dateRange = {greaterThan:dGreaterThan,lessThan:dLessThan}
		else
			return false;
	}
	//////////////////////////////
	this.getTimeRange = function () {
		return timeRange;
	}
	this.setTimeRange = function (dGreaterThan,dLessThan) {
		if(!dGreaterThan) dGreaterThan='';
		if(!dLessThan) dLessThan='';

		if((dGreaterThan=='' || isTime(dGreaterThan)) && (dLessThan=='' || isTime(dLessThan)))
			timeRange = {greaterThan:dGreaterThan,lessThan:dLessThan}
		else
			return false;
	}
	//////////////////////////////
	this.getAllowMarkup = function () {
		return allowMarkup.valueOf();
	}
	this.setAllowMarkup = function (bAllowMarkup) {
		if(bAllowMarkup)
			allowMarkup = new Boolean(1);
		else
			allowMarkup = new Boolean(0);
	}
	//////////////////////////////
	this.getType = getType;
	//////////////////////////////
	this.getErrors = function () {
		return errors;
	}
	this.getErrorMessage = function () {
		return errors.message;
	}
	this.setErrors = function (sCode, sMessage) {
		errors.code = sCode;
		errors.message = sMessage;
	}
	//////////////////////////////
	this.getCustomErrorMessage = function () {
		return customErrorMessage;
	}
	this.setCustomErrorMessage = function (sMessage) {
		if(sMessage != '')
			customErrorMessage = new String(sMessage);
		else
			return false;
	}
	//////////////////////////////

	
	/******************************************************
	****************** Private Methods *****************
	******************************************************/
	
	/***************** Getter/Setter Methods ****************/
	var getDOMElement = function (sElementName) {
		if( document.forms[sElementName] )
			return document.forms[sElementName];
		else 
			return false;
	}
	
	var getValue = function (oDOMHandler) {
		var sElementValue;
		var sElementName = self.getName();
		
		//If type is select, text, textarea, password, hidden, button
		if(oDOMHandler.elements[sElementName].type) {
			if(/^(select|select-multiple)$/i.test(oDOMHandler.elements[sElementName].type)) {
				//cycle through each option
				for(var i=0; i<oDOMHandler.elements[sElementName].options.length; i++) {
					//If this option is checked
					if(oDOMHandler.elements[sElementName].options[i].selected) {
						sElementValue = listAppend(sElementValue, oDOMHandler.elements[sElementName].options[i].value);
					}
				}
			} 
			else if(/^(checkbox|radio)$/i.test(oDOMHandler.elements[sElementName].type)) {
				//cycle through each option
				if(oDOMHandler.elements[sElementName].checked) {
					sElementValue = oDOMHandler.elements[sElementName].value;
				}
			} 
			else {
				sElementValue = oDOMHandler.elements[sElementName].value;
			}
		} else if(oDOMHandler.elements[sElementName].length && oDOMHandler.elements[sElementName].length > 0) {
			//If type is checkbox or radio
			if(/^(checkbox|radio)$/i.test(oDOMHandler.elements[sElementName][0].type)) {
				//cycle through each option
				for(var i=0; i<oDOMHandler.elements[sElementName].length; i++) {
					//If this option is checked
					if(oDOMHandler.elements[sElementName][i].checked) {
						sElementValue = listAppend(sElementValue, oDOMHandler.elements[sElementName][i].value);
					}
				}
			}
		}

		if(!sElementValue) sElementValue = "";
		
		return sElementValue;
	}

	var getType = function (oDOMHandler) {
		var sElementName = self.getName();
		
		//If type is select, text, textarea, password, hidden, button
		if(oDOMHandler.elements[sElementName].type) {
			return oDOMHandler.elements[sElementName].type;
		} else if(oDOMHandler.elements[sElementName][0].type) {
			return oDOMHandler.elements[sElementName][0].type;
		} else {
			return false;
		}
	}

	var countSelections = function (oDOMHandler) {
		var sElementName = self.getName();
		var sSelectionCount = 0;
		
		//If type is select, text, textarea, password, hidden, button
		if(oDOMHandler.elements[sElementName].length) {
			for(var i=0; i<oDOMHandler.elements[sElementName].length; i++) {
				if(oDOMHandler.elements[sElementName][i].checked)
					sSelectionCount++;
			}
		}
		
		return sSelectionCount;
	}


	/***************** Test Methods ****************/
	var isNull = function (sValue) {
		if(sValue == null)
			return true;
		else if(trim(sValue) == "")
			return true;
		else
			return false;
	}
	var isMarkup = function (sValue) {
		var matchPattern = /<\/?[^<]*>/;
		
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isEmail = function (sValue) {
		var matchPattern = /^\w(\w|\.|\-|\'|\+)*@(\w|\.|\-)+\.(\w|\.){2,4}$/;		// *OLD* /^\w\S*@.+\..{2,3}$/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isCreditCard = function (sValue, sCardType) {
		var matchPattern = /\d+/;
		
		if(matchPattern.test(sValue)) {
			var lengthIsValid = false;
			var prefixIsValid = false;
			var prefixRegExp;
		
			switch(sCardType) {
			  case "mastercard":
				lengthIsValid = (sValue.length == 16);
				prefixRegExp = /^5[1-5]/;
				break;
		
			  case "visa":
				lengthIsValid = (sValue.length == 16 || sValue.length == 13);
				prefixRegExp = /^4/;
				break;
		
			  case "amex":
				lengthIsValid = (sValue.length == 15);
				prefixRegExp = /^3(4|7)/;
				break;
		
			  default:
				return false;
			}
			
			if(prefixRegExp.test(sValue) && lengthIsValid) {
				var numberProduct;
				var numberProductDigitIndex;
				var checkSumTotal = 0;
			
				for (digitCounter = sValue.length - 1; digitCounter >= 0; digitCounter--) {
				  checkSumTotal += parseInt (sValue.charAt(digitCounter));
				  digitCounter--;
				  numberProduct = String((sValue.charAt(digitCounter) * 2));
				  for (var productDigitCounter = 0;	productDigitCounter < numberProduct.length; productDigitCounter++) {
					checkSumTotal += 
					  parseInt(numberProduct.charAt(productDigitCounter));
				  }
				}
			
				return (checkSumTotal % 10 == 0);
			} else {
				return false;
			}
		} else {
			return false;
		}
	}
	//////////////////////////////
	var isInt = function (sValue) {
		var matchPattern = /^-?\d+$/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isNumber = function (sValue) {
		var matchPattern = /^-?\d+\.?\d*$/;
		if(matchPattern.test(sValue))
			if(/\.$/.test(sValue))
				return false;
			else
				return true;
		else
			return false;
	}
	//////////////////////////////
	var isString = function (sValue) {
		if(typeof(sValue) == 'string')
			return true;
		else 
			return false;
	}
	//////////////////////////////
	var isMoney = function (sValue) {
		var matchPattern = /^((\$\-?\d*)|(\$\-?\d*\.\d{2})|(\-?\d*)|(\-?\d*\.\d{2}))$/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isDate = function (sValue) {
		var matchPattern_mmddyyyy = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{4}$/;
		var matchPattern_mmddyy = /^\d{1,2}(\-|\/|\.)\d{1,2}\1\d{2}$/;
		var matchPattern_yyyymmdd = /^\d{4}(\-|\/|\.)\d{1,2}\1\d{1,2}$/;
		if(matchPattern_mmddyyyy.test(sValue) || matchPattern_mmddyy.test(sValue) || matchPattern_yyyymmdd.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isTime = function (sValue) {
		var matchPattern = /^[0-1]?[0-9]:[0-5][0-9][A|a|P|p][M|m]$/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isUSPhone = function (sValue) {
		var matchPattern = /(^\([1-9]\d{2}\)\s?\d{3}\-\d{4}$)|(^[1-9]\d{2}\-\d{3}\-\d{4}$)/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isUSZip = function (sValue) {
		var matchPattern = /(^\d{5}$)|(^\d{5}-\d{4}$)/;
		if(matchPattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isRegEx = function (sValue) {
		if(pattern.test(sValue))
			return true;
		else
			return false;
	}
	//////////////////////////////
	var isDataValid = function (sValue) {
		var sDataType = self.getDataType();

		if(sDataType == "string") {
			return new Array(isString(sValue), "string");
		} else if(sDataType == "int") {
			return new Array(isInt(sValue), "integer");
		} else if(sDataType == "number") {
			return new Array(isNumber(sValue), "number");
		} else if(sDataType == "money") {
			return new Array(isMoney(sValue), "money");
		} else if(sDataType == "date") {
			return new Array(isDate(sValue), "date");
		} else if(sDataType == "time") {
			return new Array(isTime(sValue), "time");
		} else if(sDataType == "email") {
			return new Array(isEmail(sValue), "email address");
		} else if(sDataType == "creditcard") {
			return new Array(isCreditCard(sValue, cardType), cardType + " credit card");
		} else if(sDataType == "usphone") {
			return new Array(isUSPhone(sValue), "US telephone");
		} else if(sDataType == "uszip") {
			return new Array(isUSZip(sValue), "US zip code");
		} else if(sDataType == "regex") {
			return new Array(isRegEx(sValue), "regex");
		} else {
			return new Array(true,"");
		}
	}
	//////////////////////////////
	
	
	/***************** Format Methods ****************/
	var trim = function (sValue) {
		//validate input value
		if (typeof(sValue) != "string") { return sTrimmedString; }

		//trim whitespaces
		var sTrimmedString = sValue.replace(/^\s*/, "");	//left side
		sTrimmedString = sTrimmedString.replace(/\s*$/, "");	//right side
		
		return sTrimmedString;
	}
	
	var listAppend = function (sList, sValue, sDelimiter) {
		if(!sDelimiter)	sDelimiter = ",";

		if(sList) {
			var workingArray = sList.split(sDelimiter);
			workingArray.push(sValue);
			return workingArray.join(sDelimiter);
		} else {
			return sValue;
		}
	}
	
	this.init(sName, sForm);
}

