/***********************************************************************************************
filename    : validForm2.js
description : generic form validation script
author      : Phil Ewington (phil@n-igma.net) - http://www.n-igma.net
************************************************************************************************
revision history
************************************************************************************************
history     : added ability to remove required fields (1.1)
history     : added xOf functionality for checkboxes (1.1)
history     : added Focus() on error and style change via classes (1.2)
history     : added errorHandler functionality; now runs a custom function on error (1.2)
history     : added validation for hidden fields; useful when forms have additional fields in 
              popup windows (1.2)
history     : changed to OOP and added taskList to ensure correct order of validation (2)
history     : moved type validation to custom functions to save unwanted overhead (2)
history     : moved style changes to errHandler (2)        
history		: updated xOf to handle multiple xOf checks in form
history		: updated addItem and validation functions to make an alert message optional
			  (2.1) (AH)
history		: changed to stop the script trying to focus on hidden or disabled fields
history		: changed removeItem functions to allow flag to suppress alert if not existing
history		: updated required functions to skip through to the end function  if there is no 
			  error msg supplied (WM)
history		: added error checking for warning developer if they have faild to provide to BOTH 
			  sErrorMsg & sFunction (WM)
history		: added bFocus in to constant values for validate, and checked/set in processInvalidField (WM 2009-09-29)
************************************************************************************************/

// object constructor
function validForm(formIndex, sEndFunction, sDebug, sHighlightClass, bCollateMessages)
{
    // initialise object properties
    this.taskList = new Array();
    this.formIndex = formIndex;
    this.endFunction = sEndFunction;
    this.submitted  = false;
	this.highlightClass = (sHighlightClass == null) ? false : sHighlightClass;
	this.asErrorMessages = new Array();
	this.debugMode = (sDebug == 1) ? true : false;	
	this.bCollateMessages = (bCollateMessages == null) ? true : bCollateMessages;

    // define object methods
    this.addItem = addItem;                         // adds required field to tasklist
    this.xOfAddItem = xOfAddItem;                   // adds an xOf element to the tasklist
    this.removeItem = removeItem;                   // removes an item from tasklist
    this.validate = validate;                       // start validation process
	this.processInvalidField = processInvalidField;	// function runs when an invalid field is encounted

}

// adds a required field to the taskList
function addItem(sField, sErrorMsg, sFunction, sErrHandler)
{
	sDebugMessage = 'Added to ValidForm:\n------------------------\nField: '+sField;
    this.taskList[this.taskList.length] = new Array;
    this.taskList[this.taskList.length-1][0] = 'required';
    this.taskList[this.taskList.length-1][1] = sField;
	if (sErrorMsg) {
		sDebugMessage += '\nisMandatory Message: '+sErrorMsg;
    	this.taskList[this.taskList.length-1][2] = sErrorMsg;
	}
    if (sFunction) {
		sDebugMessage += '\nBespoke Validation Function: '+sFunction;		
        this.taskList[this.taskList.length-1][3] = sFunction;
    }
    if (sErrHandler) {
		sDebugMessage += '\nBespoke Error Handler: '+sFunction;		
        this.taskList[this.taskList.length-1][4] = sErrHandler;
    }
	if(this.debugMode) { alert(sDebugMessage); }
}

// removes an item from the taskList by building a new array WITHOUT the one you had
function removeItem(sField)
{
	asNewTaskList = new Array();
	bFound = false;
    for (var i = 0; i < this.taskList.length; i++) 
	{
        if (this.taskList[i][1] != sField)
		{
			asNewTaskList.push(this.taskList[i]);
        }
		else
		{
			bFound = true;
		}
    }
	this.taskList = asNewTaskList;
	if(!bFound) 
	{ 
		if (this.debugMode) alert("Field "+ sField + ", could not be removed from ValidForm tasklist as it could not be found!\n\n" + this.taskList.toString());
	}
    return bFound;
}

// adds an xOf element to the taskList
function xOfAddItem(sField, iCount, iMax, sErrorMsg, sFunction, sErrHandler)
{
    this.taskList[this.taskList.length]= new Array();
    this.taskList[this.taskList.length-1][0] = 'xOf';
    this.taskList[this.taskList.length-1][1] = sField;
    this.taskList[this.taskList.length-1][2] = iCount;
    this.taskList[this.taskList.length-1][3] = iMax;
    this.taskList[this.taskList.length-1][4] = sErrorMsg;
	sDebugMessage = 'Added to ValidForm (xOf):\n------------------------\nField: '+sField;
	if (sFunction) {
		sDebugMessage += '\nCustom Function: ' + sFunction;
        this.taskList[this.taskList.length-1][5] = sFunction;
    }
	if (sErrHandler) {
		sDebugMessage += '\nCustom Function: ' + sErrHandler;
        this.taskList[this.taskList.length-1][6] = sErrHandler;
    }
	if(this.debugMode) { alert(sDebugMessage); }
}

function processInvalidField(oField, sMsg, sErrorHandler)
{
	if(sMsg)
	{
		if(this.highlightClass !== false)
		{
			oField.className += ' ' + this.highlightClass;
		}
		if(this.bFocus) //check to see if we should focus on this item, takes account of last item being focused when using bCollateMessages //WM 2008-09-29
		{
			if(oField.type != "hidden" && !oField.disabled) {
				oField.focus();
			}
			this.bFocus = false;
		}
		if(this.bCollateMessages)
		{
			if(this.asErrorMessages.toString().indexOf(sMsg) == -1)
			{
				this.asErrorMessages.push(sMsg);
			}
			return true;
		}
		else
		{
			this.submitted = false;
			if (sErrorHandler) {
				eval(sErrorHandler);
			}
			alert(sMsg);
			return false;
		}
	}
	return true;
}

// validates ietms in taskList
function validate() {

	if(this.debugMode) { alert('Starting Validation'); }

	// nothing to do if taskList is empty
	if (!this.taskList.length) {
		if(this.debugMode) { alert('No fields to validate.'); }		
		return true;
	}
	
	this.asErrorMessages = new Array();
	this.bFocus = true; //make sure we focus on the first item //WM 2008-09-29
	
	// do not allow submit button to be pressed more than once
	if (this.submitted) {
		alert('The form has already been submitted, please wait for a response from the remote server.');
		return false;
	}

	for (var i=0; i<this.taskList.length; i++) {
	
		// set current field from taskList
		if(isNaN(this.formIndex))
		{
			currentField = eval('document.forms[\''+this.formIndex+'\'].elements[\''+ this.taskList[i][1] + '\']');
		}
		else
		{
			currentField = eval('document.forms['+this.formIndex+'].elements[\''+ this.taskList[i][1] + '\']');
		}

		if(currentField == null)
		{
			if(this.debugMode) { alert('Cannot find field: '+this.taskList[i][1]); }
		}
		else
		{
			if(this.debugMode) { alert('Validating Field: '+this.taskList[i][1] + ' ('+currentField.type+')'); }

			// if a highlight class is set, remove it here (it will get added again by processInvalidField if necessary
			if(this.highlightClass && currentField.className != undefined)
			{
				oRegExp = new RegExp(this.highlightClass);
				currentField.className = currentField.className.replace(oRegExp, '');
			}

			// determine validationType
			switch (this.taskList[i][0]) {
	
				case "required":
				
					/* check that developers have not called addItem with an invalid parameter combination */
					if(!this.taskList[i][2] && !this.taskList[i][3])
					{
						alert('You cannot supply a non-required field without and end function.');
						this.submitted = false;
						return false;
					}
					
					// validation of text,password,file,textarea,hidden fields
					if (currentField.type == "text" || currentField.type == "password" ||
						currentField.type == "textarea" || currentField.type == "file" ||
						currentField.type == "hidden") {
						if (!currentField.value || currentField.value == null) {
							//focus on field if not hidden or disabled
							if(!this.processInvalidField(currentField, this.taskList[i][2], this.taskList[i][4])) { return false; }
						}
						/* custom function check
						if(currentField.value && currentField.value != null)
						{
							if (this.taskList[i][3] && !eval(this.taskList[i][3])) {
								this.submitted = false;
								//errHandler
								if (this.taskList[i][4]) {
									eval(this.taskList[i][4]);
								}
								return false;
							}
						}*/
					}
	
					// validation of select lists
					if (currentField.type == "select-one" || currentField.type == "select-multiple") 
					{
						if (currentField.options[currentField.selectedIndex].value == "null") 
						{
							if(!this.processInvalidField(currentField, this.taskList[i][2], this.taskList[i][4])) { return false; }
						}
						// custom function check
						if (this.taskList[i][3] && !eval(this.taskList[i][3])) {
							this.submitted = false;
							//errHandler
							if (this.taskList[i][4]) {
								eval(this.taskList[i][4]);
							}
							return false;
						}
					}

					// validation of checkboxes
					if (currentField.type == "checkbox") {
						if (!currentField.checked) {
							if(this.taskList[i][2]) {
								alert(this.taskList[i][2]);
								//errHandler
								if (this.taskList[i][4]) {
									eval(this.taskList[i][4]);
								}
								return false;							
							}
						}
						/*
						if(currentField.value && currentField.value != null)
						{					
							// custom function check
							if (this.taskList[i][3] && !eval(this.taskList[i][3])) {
								this.submitted = false;
								//errHandler
								if (this.taskList[i][4]) {
									eval(this.taskList[i][4]);
								}
								return false;
							}
						}*/
					}
	
					// validation of radio buttons
					if (currentField.length && currentField[0].type == "radio") {
						var optionSelected = false;
						for (var j = 0; j < currentField.length; j++) {
							if (currentField[j].checked) {
								optionSelected = true;
								break;
							}
						}
						if (!optionSelected) {
							if(this.taskList[i][2]) {
								alert(this.taskList[i][2]);
								this.submitted = false;
								//errHandler
								if (this.taskList[i][4]) {
									eval(this.taskList[i][4]);
								}
								return false;
							}
						}
						/*if(optionSelected)
						{					
							// custom function check
							if (this.taskList[i][3] && !eval(this.taskList[i][3])) {
								this.submitted = false;
								//errHandler
								if (this.taskList[i][4]) {
									eval(this.taskList[i][4]);
								}
								return false;
							}
						}*/
					}
					
					// custom function call
					// if at this point, validation has been successful
					// so far. Custom functions are mainly designed for 
					// validation of text field values
					if (this.taskList[i][3] && currentField.value != '')
					{
						// look for parenthesis to tell us whether
						// to evaluate function call directly
						regex = RegExp(".*\(\w\)$", "g");
						if (regex.test(regex))
						{
							// process function call directly
							fnc = this.taskList[i][3];
						}
						else
						{
							// additional validation required on field value
							fnc = this.taskList[i][3] + "('" + currentField.value + "')";
						}
						if (!eval(fnc)) {
							sValidationMsg = (this.taskList[i][2]) ? this.taskList[i][2] : 'Please enter a valid value for: ' + this.taskList[i][1];
							if(!this.processInvalidField(currentField, sValidationMsg, this.taskList[i][4])) { return false; }							
						}
	
					}
			
					break;
	
				case "xOf":
					var noSelected = 0;
					for (var k = 0; k < currentField.length; k++) {
						if (currentField[k].checked) {
							noSelected++;
						}
					}
					// check for max value
					if (noSelected < this.taskList[i][2] || 
						(this.taskList[i][3] && noSelected > this.taskList[i][3])) {
						if(this.taskList[i][4]) {
							alert(this.taskList[i][4]);
						}
						this.submitted = false;
						return false;
					}
					// custom function check
					if (this.taskList[i][5] && !eval(this.taskList[i][5])) {
						this.submitted = false;
						if(!currentField.disabled) { currentField.focus(); }
						return false;
						//errHandler
						if (this.taskList[i][6]) {
							eval(this.taskList[i][6]);
						}
						return false;
					}
			}
			// end of validation type switch
		}
	}

	if(this.asErrorMessages.length)
	{
		sErrorMessage = 'Please correct the following to continue:\n\n';
		sErrorMessage += '- ' + this.asErrorMessages.join('\n- ');
		alert(sErrorMessage);
		this.submitted = false;
		return false;
	}
	
	// ensure form is only submitted once
	this.submitted = true;
	
	// check for end function
	if (this.endFunction) {
		if(this.debugMode) { alert('Running End Function Field: ' + this.endFunction); }
		return eval(this.endFunction);
	}
	return true;
}	
