/*******************************************************************
 File:			clsFormValidator.js
 Author:		Kelly Dacombe - created from unknown source
 Date:			29/03/2005
 Purpose:		Validate forms easily
 Documentation:	Technical_Overview_Form_Validation_vX.X.doc
 *******************************************************************/
function FormValidator(form){
	this.useForm(form);
	this.elements = new Array();
	this.message = "";
	this.isValid = false;
	this.select = false;
	this.focus = false;
	this.func = null;
}

function FormElement(name){
	this.name = name;
	this.required = false;
	this.trim = false; // only used with text fields
	this.stripWhite = false; // only used with text fields
	this.isValid = false;
	this.isValidType = false;
}

/** The main validate function **/
FormValidator.prototype.validate = function(){
	var element, formElement, i;
	
	if (!this.form){
		alert("FormValidator :: DEVELOPER ALERT :: validate()\n\nNo form selected!\n\nuse: obj.useForm('formName');");
		this.isValid = false;
		return false;
	} else {
		// start the validation
		this.isValid = true;
		var errNo = 0
		// loop through all form elements
		for (i=0;i<this.elements.length;i++){
			element = this.elements[i];
			formElement = this.form.elements[element.name];

			errWrite('n_' + element.name, ''); 
			errWrite('x_' + element.name, '');
			
			// return false if invalid element
			if (formElement.type == "button" || formElement.type == "submit" || formElement.type == "reset" || formElement.type == "image"){
				this.debug(this.DEBUG_DEV,"validate()","Form element '"+formElement.name+"' is of an unacceptable type.\n\nFormValidator will not validate buttons.");
				this.isValid = false;
				return false;
			}

			// validate the form element
			if ( !element.validate(formElement) ) {
				errNo = errNo + 1
				if (document.getElementById) { 
					errWrite('n_' + element.name, '<span class="red"><b>' + errNo + '.</b></span>'); 
					errWrite('x_' + element.name, '<span class="red"><b>X</b></span>');
				}
				if (element.restrictDesc > "" && element.value > "") 
					this.message = this.message + '<li><span class="nb">' + element.restrictDesc + '</span></li>'
				else 
					this.message = this.message + '<li><span class="nb">' + element.prefix + element.desc + '</span></li>'
				if (this.isValid) {
					if (formElement.focus && this.focus && !formElement.disabled) formElement.focus();
					if (formElement.select && this.select && !formElement.disabled) formElement.select();
				}
				this.isValid = false;
			}
		}
		
		// call the user defined function to validate more advanced stuff
		if (this.func){
			if (!this.func(this.form)){
				alert(this.func_alert);
				this.isValid = false;
			}
		}
		if (!this.isValid) this.showErrors();
		return this.isValid;
	}
}

// customised error messages
strNamePrefix = 'text only';
strEmailPrefix = 'a valid email address';
strCurrencyPrefix = 'a dollar amount';
strPhonePrefix = 'a valid phone number';
strSTDPhonePrefix = 'a valid phone number';
strDatePrefix = 'a date in the format dd/mm/yyyy';
strTimePrefix = 'a valid time';
strListPrefix = 'a valid item';
strDigitsPrefix = 'a number';
strNumberPrefix = 'a number';
strIntegerPrefix = 'a whole number';

/** Element validation function **/
FormElement.prototype.validate = function(element){
	var i, objForm;
	
	this.isValid = true;
	
	objForm = element.form;
	
	// check if field is empty
	this.data = !isBlank(this.value);
	this.empty = (this.value == '');
	
	if (this.required && !this.data){
		// required, no data
		this.isValid = false;
	} else if (!this.required && this.empty){
		// not required, is empty
		this.isValid = true;
	} else if (this.data){
		// has data, may or may not be required
		if (!this.restrictType){
			// no restriction
			this.isValid = true;
		} else {
			// validate the restriction using the "is" functions
			var arrNoCheck = new Array("Text", "Value", "InArray", "NotInArray", "Regex");
			if (arrNoCheck.contains(this.restrictType)) {
				this.isValidType = true;
			} else {
				this.isValidType = this.isValid = eval("is" + this.restrictType)(this.value);
				this.prefix += eval('str' + this.restrictType + 'Prefix') + " ";
			}
			
			// further checks on content
			if (this.isValid) {
				switch (this.restrictType){
					case "Text":
					case "Digits":
						// for text or digits check the length of the input
						this.prefix = "Please enter ";
						var strContentName = (this.restrictType == "Text") ? "characters" : "digits";
						if (isInteger(this.restrictCond1) && isInteger(this.restrictCond2)) {
							this.isValid = (this.value.length >= this.restrictCond1 && this.value.length <= this.restrictCond2);
							this.prefix += this.restrictCond1 + " to " + this.restrictCond2 + " " + strContentName + " (you have entered " + this.value.length + ") for ";
						} else if (isInteger(this.restrictCond1)) {
							this.isValid = (this.value.length >= this.restrictCond1);
							this.prefix += "a minimum of " + this.restrictCond1 + " " + strContentName + " (you have entered " + this.value.length + ") for ";
						} else if (isInteger(this.restrictCond2)) {
							this.isValid = (this.value.length <= this.restrictCond2);
							this.prefix += "a maximum of " + this.restrictCond2 + " " + strContentName + " (you have entered " + this.value.length + ") for ";
						}
						break;
						
					case "Date":
						// for dates, check the date
						var arrInput = this.value.split("/");
						var dateToday = new Date();
						var yN = dateToday.getFullYear(), yI = arrInput[2];
						var mN = dateToday.getMonth(), mI = arrInput[1] - 1;
						var dN = dateToday.getDate(), dI = arrInput[0];
						
						var dateTodayX = new Date(yN, mN, dN);
						var dateInput = new Date(yI, mI, dI);
						
						if (yN - yI > 200) {
							this.prefix += "within the last century";
							this.isValid = false;
						} else if (isInteger(this.restrictCond1)) { // a number of years
							this.prefix += "at least " + this.restrictCond1 + " years ago";
							if (yN - yI > this.restrictCond1) { // more than specified years
								this.isValid = true;
							} else if (yN - yI == this.restrictCond1) {
								// specified years ago this year - check months
								if (mN > mI) this.isValid = true;
								else {
									// specified years ago this month - check days
									if (dN > dI) this.isValid = true;
									else {
										// check for exact date
										if (mN == mI && dN == dI) {
											this.isValid = true;
										} else {
											this.isValid = false;
										}
									}
								}
							} else this.isValid = false;
						} else if (!isBlank(this.restrictCond1)) {
							if (this.restrictCond1 == 'future') {
								this.prefix += "after today's date";
								if (Date.parse(dateInput) > Date.parse(dateTodayX)) this.isValid = true;
								else this.isValid = false;
							} else if (this.restrictCond1 == 'today or after') {
								this.prefix += "on or after today's date";
								if (Date.parse(dateInput) >= Date.parse(dateTodayX)) this.isValid = true;
								else this.isValid = false;
							} else if (this.restrictCond1 == 'past') {
								this.prefix += "before today's date";
								if (Date.parse(dateInput) < Date.parse(dateTodayX)) this.isValid = true;
								else this.isValid = false;
							} else if (this.restrictCond1 == 'today or before') {
								this.prefix += "on or before today's date";
								if (Date.parse(dateInput) <= Date.parse(dateTodayX)) this.isValid = true;
								else this.isValid = false;
							}
						}
						break;
						
					case "Number":
					case "Integer":
					case "Currency":
						// for numbers, check the number
						if (isInteger(this.restrictCond1) && isInteger(this.restrictCond2)) {
							this.isValid = (this.value >= this.restrictCond1 && this.value <= this.restrictCond2);
							this.prefix += "between " + this.restrictCond1 + " and " + this.restrictCond2;
						} else if (isInteger(this.restrictCond1)) {
							this.isValid = (this.value >= this.restrictCond1);
							this.prefix += "greater than " + this.restrictCond1;
						} else if (isInteger(this.restrictCond2)) {
							this.isValid = (this.value <= this.restrictCond2);
							this.prefix += "less than " + this.restrictCond2;
						}
						break;
					
					case "Value":
						this.isValid = eval(this.value == this.restrictCond1);
						break;
						
					case "InArray":
						this.isValid = this.restrictCond1.contains(this.value);
						break;
						
					case "NotInArray":
						this.isValid = !this.restrictCond1.contains(this.value);
						break;
						
					case "Regex":
						this.restrictCond1 = (typeof this.restrictCond1 == "string") ? new RegExp(this.restrictCond1,"i") : this.restrictCond1;
						this.isValid = this.restrictCond1.test(this.value);
						break;
				}
			}
			if (!arrNoCheck.contains(this.restrictType)) {
				this.prefix += " for ";
			}
		}
	} else {
		this.isValid = true;
	}
	
	return this.isValid;
}

/** Restrict the possible input values for a form element **/
FormElement.prototype.restrict = function(type,cond1,cond2){
	this.restrictType = type;
	this.restrictCond1 = cond1;
	this.restrictCond2 = cond2;
}

/** Get the value of the form element **/
FormValidator.prototype.getValues = function(){
	// loop through all form elements
	for (j=0;j<this.elements.length;j++){
		element = this.elements[j];
		formElement = this.form.elements[element.name];
		
		element.type = formElement.type;
		if (!element.type) element.type = formElement[0].type;
		switch (element.type){
			case "text":
			case "textarea":
			case "file":
				element.prefix = 'Please enter ';
				element.value = formElement.value;
				if (element.stripWhite) element.value = element.value.replace(/\s/g,'');
				if (element.trim) element.value = element.value.trim();
				break;
	
			case "checkbox":
			case "radio":
				element.prefix = 'Please select ';
				element.value = '';
				if (formElement.length) {
					for (i=0;i<formElement.length;i++){
						if (formElement[i].checked){
							if (element.value > '') {
								element.value = element.value + ',' + formElement[i].value;
							} else {
								element.value = formElement[i].value;
							}
						}
					}
				} else {
					if (formElement.checked) {
						element.value = formElement.value;
					}
				}
				break;
	
			case "select-one":
			case "select-multiple":
				element.prefix = 'Please select ';
				element.value = '';
				for (i=0;i<formElement.length;i++){
					if (formElement[i].selected){
						if (element.value > '') {
							element.value = element.value + ',' + formElement[i].value;
						} else {
							element.value = formElement[i].value;
						}
					}
				}
				break;
	
			default:
				element.prefix = 'Please enter ';
				element.value = formElement.value;
				break;
		}
	}
}

/** Define the form **/
FormValidator.prototype.useForm = function(form){
	if (typeof form != "undefined"){
		if (typeof form == "object"){
			this.form = form;
		} else if (typeof form == "string" || typeof form == "number"){
			this.form = document.forms[form];
		} else {
			alert("FormValidator :: DEVELOPER ALERT :: useForm()\n\nInvalid form definition");
		}
	}
}

/** Functions to add and remove single elements **/
FormValidator.prototype.add = function(name){
	if (!this[name] && !isBlank(name)){
		this[name] = new FormElement(name);
		this.elements[this.elements.length] = this[name];
	}
}
FormValidator.prototype.remove = function(element){
	var i, temp_array;
	temp_array = new Array();
 	for (i=0;i<this.elements.length;i++){
 		if (this.elements[i].name != element){
			temp_array[temp_array.length] = this.elements[i];
     		}
  	}
	this.elements = temp_array;
	delete this[name];
}

/** Functions to add and remove all elements **/
FormValidator.prototype.addAll = function(){
	var i, field;

	if (!this.form){
		alert("FormValidator :: DEVELOPER ALERT :: addAll()\n\nNo form selected!\n\nuse: obj.useForm('formName');");
	} else {
		// run through all of the form elements and add each one that has a name and is not a button
		for (i=0;i<this.form.elements.length;i++){
			field = this.form.elements[i];
			if (!isBlank(field.name)){
				if (field.type != "hidden" && field.type != "button" && field.type != "submit" && field.type != "reset" && field.type != "image"){
					this.add(field.name);
				}
			}
		}
		this.getValues();
	}
}
FormValidator.prototype.removeAll = function(){
	var i;

	// remove elements from validation object
	for (i in this.elements){
		delete this[this.elements[i].name];
	}

	// reset elements array
	this.elements = new Array();
}

/** Use to give an element property the same value for all elements **/
FormValidator.prototype.setGlobal = function(){
	var args, setType, setValue, element, i;
	
	args = FormValidator.prototype.setGlobal.arguments;
	if (args.length <= 1){
		alert("FormValidator :: DEVELOPER ALERT :: setGlobal()\n\nIncorrect number of arguments supplied.");
		return false;
	}
	
	setType = args[0];
	setValue = args[1];
	
	for (i=0;i<this.elements.length;i++){
		element = this.elements[i];
		element[setType] = setValue;
	}
}

/** Adds a user defined function for other validation checks **/
FormValidator.prototype.setUserFunction = function(func,func_alert){
	this.func = func;
	this.func_alert = func_alert;
}

/** trim & contains for js **/
String.prototype.trim = function(){
	return this.replace(/^\s+/,'').replace(/\s+$/,'');
}
Array.prototype.contains = function(value){
	for (var i=0;i<this.length;++i)
		if (this[i].toString().toLowerCase() == value.toString().toLowerCase()) return true;
	return false;
}


/** Validation checks **/
function isNumber(s) {return /^[-?|+?]*[\d+\.?\d]*$/ .test(s) && !isNaN(s);}
function isInteger(s) {return /^-?\d*$/ .test(s) && !isNaN(s);}
function isCurrency(s) {return /^\-?(\d+|((\d{1,3})?((,\d{3})*)))?((\.\d{1,2})?)$/.test(s);}
function isName(s) {return /^[A-Za-z\-"'"\s]+$/.test(s);}
function isDigits(s) {return isInteger(s) && (s >= 0);}
function isPhone(s) {return (/^[\d \-\+\(\)]+$/.test(s)) && (s.length > 4);}
function isSTDPhone(s) {
	if (/^[\d \-\+\(\)]+$/.test(s)) {
		var n = s.replace(/[ \-\+\(\)]/gi, "");
		if (n.length == 10 || (s.charAt(0) == '+' && n.length == 11)) return true;
		else return false;
	} else return false;
}
function isList(s) {return /^((( )*)((\d)+)(( )*)((,)+)(( )*))*(((\d)*)(( )*))$/.test(s);}
function isEmail(s) {
	var emailPat = /^(.+)@(.+)$/, specialChars="\\(\\)<>@,;:\\\\\\\"\\.\\[\\]"
	var validChars = "\[^\\s" + specialChars + "\]"
	var quotedUser = "(\"[^\"]*\")"
	var ipDomainPat = /^\[(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\]$/
	var atom = validChars + '+'
	var word = "(" + atom + "|" + quotedUser + ")"
	var userPat = new RegExp("^" + word + "(\\." + word + ")*$")
	var domainPat = new RegExp("^" + atom + "(\\." + atom +")*$")
	var matchArray = s.match(emailPat)
	if (matchArray == null) return false;
	var user = matchArray[1]
	var domain = matchArray[2]
	if (user.match(userPat) == null) return false;
	var IPArray = domain.match(ipDomainPat)
	if (IPArray != null) {
		for (var i=1;i<=4;i++) {
			if (IPArray[i] > 255) return false;
		}
		return true;
	}
	var domainArray = domain.match(domainPat)
	if (domainArray == null) return false;
	var atomPat = new RegExp(atom,"g")
	var domArr = domain.match(atomPat)
	var len = domArr.length
	if (domArr[domArr.length-1].length < 2 || domArr[domArr.length-1].length > 4) return false;
	if (len < 2) return false;
	return true; 
}
function isLeapYear(s) {return ((s%4 == 0 && s%100 != 0) || (s%4 == 0 && s%400 == 0));}
function isDate(s){
	if (! /^\d{1,2}\/\d{1,2}\/\d{4}$/.test(s)) return false;
	if (s.indexOf("/", s.indexOf("/")) == -1) return false;
	var dArr = s.split("/");
	if (!isYear(dArr[2])) return false;
	if (!isNumberInLimits(dArr[1], 1, 12)) return false;
	if (dArr[0] < 1 || dArr[0] > daysInMonth(dArr[1], dArr[2])) return false;
	return true;
}
function isTime(s) {return /^\d{1,2}:\d{2}$/.test(s);}
function isBlank(s){return /^\s*$/.test(s);}

/** Functions for displaying errors to user **/
FormValidator.prototype.showErrors = function(){
	var errorMessage = '<h1 class="red">Errors</h1><ol class="errors">' + this.message + '</ol>'
	if (document.getElementById) {
		errWrite("validation", errorMessage);
	} else {
		open_window("/includes/form_error_popup.asp", "error");
	}
	document.location = "#top"
}
function errWrite(divRef, divTxt){
	if (document.getElementById) {
		if (document.getElementById(divRef)) 
			document.getElementById(divRef).innerHTML=divTxt;
	} else if (document.all) {
		document.all[divRef].innerHTML=divTxt;
	}
}
/** find the right option and check it **/
FormValidator.prototype.checkAnOption = function(cGroupName, cValue) {
	var i;
	var cGroup = this.form.elements[cGroupName]
	for (i = 0; i < cGroup.length; i++) 
		if (cGroup[i].value == cValue) cGroup[i].checked = true;
}


/**** DEBUGGING ****/
FormValidator.prototype.describe = function(){
	var element, i;
	var strMsg = "";

	if (this.elements.length == 0){
		strMsg += "No form elements!";
	} else {
		strMsg += "List of form elements:\n";
		for (i=0;i<this.elements.length;i++){
			element = this.elements[i];
			strMsg += " - "+element.name+" : "+element.value+"\n";
		}
	}
	alert(strMsg);
}
