/*
$Id: m4.form.validation.js,v 1.12 2008-10-02 15:05:51 cde Exp $
*/

/*****************************************************************************
m4Validation JavaScript validation framework
by Chris Exon
******************************************************************************/

/******************************************************************************
*** CONSTRUCTOR : m4Validation
***
*** Stores the pages validators
******************************************************************************/

var m4Validation=Class.create({
	validationFields : ["input[validator]","input[required]","input[range]","select[required]"],

	initialize: function(form,options) 
	{
		if (!options) var options={};
	
		this.guid=(options.guid || "");
		this.validators=new Array();
		this.errorMessages ="";
		this.individualErrors=(options.individualErrors==undefined || options.individualErrors);
		this.highlightErrors=(options.highlightErrors==undefined || options.highlightErrors);
		this.tooltipErrors=(options.errorType==undefined || options.errorType);
		this.validatorsLoaded=false;
		this.form=form;

		//Set an event listener which gets called when 
		CustomEvent.observe(this.guid+"validator:failedEvent",options.failedEvent);
	},

	/******************************************************************************
	*** m4Validation.destroy
	***
	*** Destroy the Validation object
	******************************************************************************/
	destroy : function()
	{
		CustomEvent.stopObserving(this.guid+"validator:failedEvent");
		this._clearValidators();
	},

	/******************************************************************************
	*** m4Validation.__loadValidators
	***
	*** Load the form
	******************************************************************************/
	_loadValidators : function()
	{
		if (this.validatorsLoaded) return;
		this.addValidators(this.form);
	},


	/******************************************************************************
	*** m4Validation.__loadValidators
	***
	*** Load the form
	******************************************************************************/
	addValidators : function(container)
	{
		var arrInputs=$css(this.validationFields,container);
	 
		//Find all input controls with an 'validator' attribite.
		for (var i=0;i<arrInputs.length;i++)
		{
			var objInput=arrInputs[i];
			
			var strRange=objInput.getAttribute("range");
			var strValidator=(objInput.getAttribute("validator") || "");				
			var blnRequired=false;
				
			//If the input has the required property then read it
			if (objInput.getAttribute("required")){
				var strRequired=objInput.getAttribute("required").toLowerCase();
				blnRequired=(strRequired=="1" || strRequired=="true") ? true : false;
			}
				 
			//Add the validator    
			this._addValidator(objInput,strValidator,strRange,blnRequired);
		}
	},

	/******************************************************************************
	*** m4Validation._addValidator
	***
	*** Add a validator
	******************************************************************************/
	_addValidator : function(objInput,strValidator,strRange,blnRequired)
	{
		//Add a new validator to the collection
		this.validators.push(new m4Validator(objInput,strValidator,strRange,blnRequired,this));
		//Set the flag when at least one validator has been loaded
		this.validatorsLoaded=true;
	},
	
	/******************************************************************************
	*** m4Validation.removeValidator
	***
	*** Remove validators from objects
	******************************************************************************/
	removeValidator : function(objInput)
	{
		for (var i=0;i<this.validators.length;i++)
		{
			if (this.validators[i].domObject==objInput)
			{
				var validator=this.validators.splice(i,1);
				delete validator;
				break;
			}
		}	
	},
	

	/******************************************************************************
	*** m4Validation.validateForm
	***
	*** Validate the form 
	******************************************************************************/
	validateForm : function()
	{
		//Load the validators
		this._loadValidators();

		this.errorMessages="";
	
		var blnValidationFailed=false;
		var firstFailedComponent=null;
	
		//Reset background colour of all form elements with validation
		this._resetValidationComponents();
	
		//Validate all m4 form controls
		for (var i=0;i<this.validators.length;i++)
		{
			if (this.validators[i].validate()==false){
				blnValidationFailed=true;
				if (!firstFailedComponent) firstFailedComponent=this.validators[i].domObject; 
			}
		}
		
		//Focus the 1st component that failed validation
		if (firstFailedComponent)
		{
			m4.focusComponent(firstFailedComponent);
		}
		
		return (blnValidationFailed==false);
	},

	/******************************************************************************
	*** m4Validation._clearValidators
	***
	*** Reset the components with validation
	******************************************************************************/
	_clearValidators : function ()
	{
		Object.purge(this.validators);
		this.validators=[];
	},

	/******************************************************************************
	*** m4Validation._resetValidationComponents
	***
	*** Reset the components with validation
	******************************************************************************/
	_resetValidationComponents : function()
	{
		for (var x=0;x<this.validators.length;x++)
		{     
			this.validators[x].reset();
		}
	}
});

/******************************************************************************
*** CONSTRUCTOR : m4Validator
***
*** Validator object
******************************************************************************/

var m4Validator=Class.create({
	
	initialize: function(domOject,strValidator,strRange,blnRequired,validatorsGroup) 
	{
		this.domObject=domOject;
		this.validator=strValidator;
		this.range=strRange;
		this.required=blnRequired;
		this.validatorsGroup=validatorsGroup;
		this.errorSpan=null;

		if (this.validatorsGroup.tooltipErrors==false)
		{
			this.createErrorSpan();
		}
	},

	/******************************************************************************
	*** m4Validator.reset
	***
	*** Reset the input field
	******************************************************************************/
	reset : function()
	{
		//If error spans are being used, remove the span
		if (this.validatorsGroup.tooltipErrors==false)
		{
			//Find the error span
			var objErrorSpan=this.domObject.next();
			while (objErrorSpan && !objErrorSpan.hasClassName("m4ValidationMessage"))
			{
				objErrorSpan=objErrorSpan.next();
			}
			
			objErrorSpan.innerHTML="";
			objErrorSpan.removeClassName("m4ValidationMessageTooltip");
			objErrorSpan.hide();
		}
		else{
			this.domObject.title="";
		}
		
		//Remove input highlighting
		if (this.validatorsGroup.highlightErrors==true)
		{
			this.domObject.removeClassName("inputBoxValidateFailed");
		}
	},


	/******************************************************************************
	*** m4Validator.validate
	***
	*** Validate 1 compononent
	******************************************************************************/
	validate : function()
	{
		var strValidator=this.validator;
		var blnRequired=this.required;
		var strError="";
		var strTooltip=null;
		var blnResult=true;
		var strValue=this.domObject.value;
		var blnOK=true;
	
		//Check for an empty string. If required=false then this is ok
		if (blnRequired==false && strValue=="")
		{
			//Do no validation
		}
		//A value is required but one has not been entered
		else if (blnRequired==true && strValue=="") 
		{
			strError=m4Lang.get("validateFail_required");
		}
		else
		{
			//Validate the field
			if (this.domObject.disabled==false)
			{ 

				//If the value was vaildated on the server & failed, or if its clientside validation
				//fails then set the error messages
				if (this.validateValue(strValue)==false)
				{
					strError=m4Lang.get("validateFail_"+strValidator);
					
					strTooltip=m4Lang.get("validateFail_"+strValidator+"_tip");
				}
				else
				{

					//Check if a range is specified and if so validate against it
					strError=this.validateRange(); 
				}
			}
		}

		if (strError!="")
		{
			//Shade the control that failed validation
			if (this.validatorsGroup.highlightErrors==true)
			{
				this.domObject.addClassName("inputBoxValidateFailed");
			}   
			
			//If the flag is set to show the error then 
			//display it in the SPAN next to the text box
			if (this.validatorsGroup.individualErrors==true)
			{
				if (this.validatorsGroup.tooltipErrors==false)
				{		
					var objErrorSpan= this.errorSpan;
					
					if (objErrorSpan) 
					{
						objErrorSpan.innerHTML=strError;
	
						//If a tooltip exists then set it
						if (strTooltip && strTooltip.indexOf("NOT FOUND")==-1)
						{
							objErrorSpan.title=strTooltip;
							objErrorSpan.addClassName("m4ValidationMessageTooltip");
						}
			
						objErrorSpan.show();
					}
				}
				else
				{
					this.domObject.title=strError;
				}
			}
			else
			{
				this.validatorsGroup.errorMessages+=strError+"\n";
			}
			
			blnOK=false;
			//Set focus
			this.domObject.activate();
		}
		
		
		if (!blnOK)
		{
			this.domObject.fire(this.validatorsGroup.guid+"validator:failedEvent");
		} 
		
		return blnOK;
	},
	
		
	/******************************************************************************
	*** m4Validation.createValidationErrorSpan
	***
	*** Creates a span at the rightside of the input field to be used to hold
	*** any validation methods
	******************************************************************************/
	createErrorSpan : function()
	{
		//Don't create if it already exists
		if (this.errorSpan) return;

		var objResult=document.createElement("span");
		objResult.innerHTML="";
		objResult.className="m4ValidationMessage";
		objResult.style.display="none";

		//Find the object to insert the validation message after
		//Calendar controls have buttons after them so can't just insert
		//directly after the input box
		var obj=this.domObject;
		while (obj.next())
		{
			obj=obj.next();
		}			
			
		if (browser.isIE){
			obj.insertAdjacentElement ("afterEnd",objResult);
		}
		else{
			obj.parentNode.insertBefore(objResult,obj.next());
		}  	
		
		this.errorSpan=$(objResult);
	},
	
	/******************************************************************************
	*** validateRange
	*** Method on m4Validator object
	*** 
	*** Validate a range. The range is read from the passed in controls attributes
	******************************************************************************/
	validateRange : function()
	{

		//Do range checking
		var strRange=this.range;
		var strError="";
		
		if (strRange && strRange.indexOf("-")>0)
		{
			var arrRange=strRange.split("-");
			
			//Check the 2 boundaries are number
			if (isNaN(arrRange[0]) || isNaN(arrRange[1]))
			{
				alert("Invalid range specified for "+arrInputs[i].id);
			}
			else
			{
				//Get min and max of range
				var intMin=Number(arrRange[0]);
				var intMax=Number(arrRange[1]);

				//Set the error message if the value is out of range
				if (isNaN(this.domObject.value) || this.domObject.value<intMin || this.domObject.value>intMax)
				{
					strError=m4Lang.get("validateFail_range");
					strError=strError.replace("{0}",intMin);
					strError=strError.replace("{1}",intMax);
				}
			}
		}
	
		return strError;
	},

	/******************************************************************************
	*** m4Validator.validateValue
	*** Method on m4Validator object
	*** 
	*** Validate a value
	******************************************************************************/
	validateValue : function(value) {
	
		if(this.validator=="") return true;
		
		//Run the validator, if it doesn't exist, then return true and validation
		//will be done on the serverside.
		if (m4ValidationRoutine[this.validator])
		{
			return m4ValidationRoutine[this.validator](value,this.domObject);
		}
		
		return true;
	}
});