	INITVAL	=	-2;
	PREVAL	=	-1;
	FORMVAL	=	0;
	LIVEVAL	=	1;
	//	========== OBJECT BOHVal ==========


	function BOHVal(xi_Form, xi_AddToForm,
					xi_Input, xi_Type, xi_EmptyOK, xi_ValType, xi_Scope, xi_ErrorFunc, xi_CustomFuncs)
	{
		// CustomFuncs is object containing change_pre->funcref, change_post->funcref, change_post_SkipInvalid->bool
		var	Input_elt;
		var	Form_elt;
		var	Scope_obj;
		
		if		 (typeof(xi_Form) == 'object')	Form_elt = xi_Form;
		else if	((typeof(xi_Form) == 'string')	&& !(Form_elt = document.getElementById(xi_Form)))
				alert('new BOHForm : Could not locate form ' + xi_Form);

		if(typeof(xi_Input) != 'string')
		{	
			var	spec = xi_Input;
			xi_Input = spec[0]; xi_Type = spec[1]; xi_EmptyOK = spec[2]; xi_ValType = spec[3]; 
			xi_Scope = spec[4]; xi_ErrorFunc = spec[5]; xi_CustomFuncs = spec[6];
		}

		if(!(this.Input_elt = document.getElementById(xi_Input)))
			alert('new BOHVal : Could not locate input ' + xi_Input);
		else
		{
			this.ID = this.Input_elt.id;
			if(this.Input_elt.BOHVal)	alert('Duplicate validation for input ' + this.Input_elt.id);
			else						this.Input_elt.BOHVal = this;
			
			this.Type 		= xi_Type;
			this.EmptyOK 	= xi_EmptyOK;

			switch(xi_Type)
				{
					case 'int' 		:	this.Typex		=	/^\s*\d*\s*$/;
										break;
					case 'float'	:	this.Typex		=	/^\s*\d*\.?\d*\s*$/;
										break;
					case 'money'	:	this.Typex		=	/^\d*(\.\d\d)?$/;
										break;
					case 'date'		:	this.Typex		=	/^(\d\d?)\D(\d\d?)(?:\D((?:\d|\d\d|\d\d\d\d)))?\D*$/;
										break;
					case 'time'		:	this.Typex		=	/^(\d\d?)\D?(\d\d)(?:\D(\d\d))?$/;
										break;
					case 'duration'	:	this.Typex		=	/^(\d+)(?:\D(\d\d))?(?:\D(\d\d))?\D*$/;
										break;
				}
				
			this.ValType 	= xi_ValType;
			
			if((xi_Type == 'date') || (xi_Type == 'time') || (xi_Type == 'duration'))
			{
				this.Complex = true;
				switch(xi_Type)
					{
						case	'date'		:	{	this.Input_elt.BOHObj = new O3Date(this.Input_elt.value);		break;	}
						case	'time'		:	{	this.Input_elt.BOHObj = new O3Time(this.Input_elt.value);		break;	}
						case	'duration'	:	{	this.Input_elt.BOHObj = new O3Duration(this.Input_elt.value);	break;	}
					}

			
				if((xi_ValType == 'range') || (xi_ValType == 'enum'))
				{
					this.Scope = new Array();
					for(var i = 0; i < xi_Scope.length; i++)
					{
						if(xi_Type == 'date')		Scope_obj = new O3Date(xi_Scope[i]);
						else if(xi_Type == 'time')	Scope_obj = new O3Time(xi_Scope[i]);
						else						Scope_obj = new O3Duration(xi_Scope[i]);
						
						if(!Scope_obj.Valid)		alert('Invalid scope ' + xi_Scope[i] + ' for ' + this.Input_elt.id);
						this.Scope.push(Scope_obj);
					}
				}
				else if(xi_ValType == 'function')
					this.Scope = xi_Scope;
			}
			else
				this.Scope		= xi_Scope;

			O3_AddEvent(this.Input_elt, 'change', BOHVal_Common_change_event, false);
			
			this.ErrorFunc	= xi_ErrorFunc;
			this.CustomFuncs = xi_CustomFuncs ? xi_CustomFuncs : {};
			this.LiveValSet	= false;
			
			this.BaseClass = this.Input_elt.className;
			this.FlagBaseClasses = new Object();
			this.Status = 'OK';
			

			this.Valid = true;
			if(Form_elt)
			{
				this.Form_elt = Form_elt;
				if(xi_AddToForm)
				{
					if(!Form_elt.BOHForm)	new BOHForm(Form_elt);
					Form_elt.BOHForm.addVal(this);
				}	
			}
			this.validate(INITVAL);
		}
	}	

	BOHVal.prototype.validate =	function (xi_ValMode)
	{
		// zzz_todo this needs to be smarterer to do radio buttons etc johnf
		var	value = this.Input_elt.value;
		var OldStatus = this.Status;
		var NewStatus = 'OK';
		var	done = false;
		var	event_type;
		if(xi_ValMode == null)	xi_ValMode = this.LiveValSet ? LIVEVAL : PREVAL;
		if(typeof(xi_ValMode) == 'boolean')	alert('validate called in deprecated mode'); // Remove this when bedded in
		//alert('validating ' + this.Input_elt.id);			
		if(value == "")
		{
			if(this.EmptyOK != null)
			{
				if(this.Complex)	this.Input_elt.BOHObj.set(value);
				if(!this.EmptyOK)	NewStatus = 'Empty';
				done = true;
			}
		}
		if(!done)
		{
			if(this.Typex && !value.match(this.Typex))	NewStatus = 'Invalid';

			if(this.Complex)
				if(NewStatus == 'OK')
				{
					this.Input_elt.BOHObj.set(value);
					if(!this.Input_elt.BOHObj.Valid)	NewStatus = 'Invalid';
				}
				else
				{
					this.Input_elt.BOHObj.Valid = false;	//	Keep object consistent
				}
			
			if(NewStatus == 'OK')
			{
				switch(this.ValType)
				{
					case 'regex'	:	{	
											if(!value.match(this.Scope))
												NewStatus = 'Invalid';
											break;
										}
					case 'range'	:	{	
											if(this.Scope[0] && this.Complex ? (this.Input_elt.BOHObj.cmp(this.Scope[0]) < 0) : (value < this.Scope[0]))
												NewStatus = 'Invalid';
											else if(this.Scope[1] && this.Complex ? (this.Input_elt.BOHObj.cmp(this.Scope[1]) > 0) : (value > this.Scope[1]))
												NewStatus = 'Invalid';
											break;
										}
					case 'enum'		:	{
											NewStatus = 'Invalid';
											for(var i = 0; i < this.Scope.length; i++)
											{
												if(this.Complex ? (this.Input_elt.BOHObj.cmp(this.Scope[i]) == 0) : (value == this.Scope[i]))
												{
													NewStatus = 'OK';
													break;
												}
											}
											break;
										}
					case 'function'	:	{
											//alert('calling function for ' + this.Input_elt.id);
											//alert(typeof(this.Scope));
											NewStatus = this.Scope(value, xi_ValMode, this);
											break;
										}
				}
			}
		}
		this.flag(NewStatus, xi_ValMode);
		this.Status = NewStatus;

		if(xi_ValMode == LIVEVAL)
		{
			if(this.Form_elt.BOHForm.FormVals.length)	{this.Form_elt.BOHForm.validateForm(LIVEVAL);}
		}

		if(xi_ValMode == FORMVAL && !this.LiveValSet)
		{
			switch(this.Input_elt.type)
			{
				case	'text'			:	{
												event_type = 'keyup';
												break;
											}
			}
			if(event_type)
			{
				O3_AddEvent(this.Input_elt, event_type, BOHVal_LiveVal_event, true);
			}
			this.LiveValSet = true;
		}

		return (NewStatus == 'OK') ? true : false;
	};

	BOHVal.prototype.flag =	function (xi_NewStatus, xi_ValMode)
	{
		var	Flag_elt;
		//if(this.Status == xi_NewStatus)
		//{
		//	return;
		//}
		if(this.Status != 'OK')
		{
			// If existing status is not OK reset the flag assoc with it
			if(Flag_elt = document.getElementById(this.ID + 'Flag' + this.Status))
			{
				Flag_elt.className = this.FlagBaseClasses[this.Status];
			}
		}
		if(xi_NewStatus == 'OK')
		{
			this.Input_elt.className = this.BaseClass;
		}
		else
		{
			if((Flag_elt = document.getElementById(this.Input_elt.id + 'Flag' + xi_NewStatus)) && !this.FlagBaseClasses[xi_NewStatus])
				this.FlagBaseClasses[xi_NewStatus] = Flag_elt.className;
			if(xi_ValMode > PREVAL)
			{
				this.Input_elt.className = this.BaseClass + ' Input' + xi_NewStatus;
				if(Flag_elt)	Flag_elt.className = this.FlagBaseClasses[xi_NewStatus] + ' FlagShow';
			}
		}
	}

	function BOHVal_LiveVal_event(xi_Event) { return BOH_EventTarget(xi_Event).BOHVal.validate(LIVEVAL);	}
	
	
	function BOHVal_Common_change_event(xi_Event)
	{ 
		var	Target_elt = BOH_EventTarget(xi_Event);
		
		if(Target_elt.BOHVal.CustomFuncs.change_pre)	Target_elt.BOHVal.CustomFuncs.change_pre(xi_Event);	
		Target_elt.BOHVal.validate();	// Catch browser fill which doesn't have a keystroke
		if(Target_elt.BOHVal.Status == 'OK')
		{
			if(Target_elt.BOHVal.Complex)	Target_elt.value = Target_elt.BOHObj.display();
			else if(Target_elt.BOHVal.Type == 'money' && Target_elt.value.match(/\S/)) Target_elt.value = parseFloat(Target_elt.value).toFixed(2);
		}
		if(Target_elt.BOHVal.CustomFuncs.change_post &&((Target_elt.BOHVal.Status == 'OK') || !Target_elt.BOHVal.CustomFuncs.change_post_SkipInvalid))
			Target_elt.BOHVal.CustomFuncs.change_post(xi_Event);	
	}
	function BOHVal_Complex_change_elt(xi_elt)
	{ 
		var	Target_elt = xi_elt;
		
		if(Target_elt.BOHVal.CustomFuncs.change_pre)	Target_elt.BOHVal.CustomFuncs.change_pre(xi_Event);	
		
		if(Target_elt.BOHObj.set(Target_elt.value).isValid())
			Target_elt.value = Target_elt.BOHObj.display();
		Target_elt.BOHVal.validate();	// Catch browser fill which doesn't have a keystroke
		
		if(Target_elt.BOHVal.CustomFuncs.change_post &&((Target_elt.BOHVal.Status == 'OK') || !Target_elt.BOHVal.CustomFuncs.change_post_SkipInvalid))
			Target_elt.BOHVal.CustomFuncs.change_post(xi_Event);	
	}
