/***************************************************************************************************
*
*-- Form validation script by Peter Bailey, Copyright (c) 2001-2002
*	Version 5.00b
*	Updated on May 28, 2002
*	www.peterbailey.net
*	me@peterbailey.net
*
*	IF YOU USE THIS SCRIPT, GIVE ME CREDIT PLEASE =)
*
*	Visit http://www.peterbailey.net/fValidate for more info
*
*	Please contact me with any questions, comments, problems, or suggestions
*
*	Note: This document most easily read with tab spacing set to 4
*
*******************************************************************************************************/

//	Main validation routine
function validateForm( f, bConfirm, bDisable, bDisableR, groupError, errorMode )
{
	//	Set defaults
	bConfirm = Boolean( bConfirm );
	bDisable = Boolean( bDisable );
	bDisableR = Boolean( bDisableR );
	groupError = Boolean( groupError );
	errorMode = ( errorMode ) ? parseInt( errorMode, 10 ) : 0;

	//	Init vars and fValidate object
	var params, fvCode, type;
	if ( typeof fv == 'undefined' )
	{
		var fv = new fValidate( f, errorMode, Boolean( groupError ) );
	}
	
	//	Loop through all form elements	
	var elem, i = 0;
	while ( elem = f.elements[i++] )
	{
		//	Does element have validator attribute?
		if ( fvCode = elem.getAttribute( fv.config.code ) )
		{
			//	Set params, validation type, and validation state
			params         = fvCode.split( "|" );
			type           = params[0];
			elem.validated = true;
			
			//	Valid validator type?
			if ( typeof fv[type] == 'undefined' )
			{
				fv.devError( "The validator '" + type + "' was not found.\nRequested by: " + elem.name );
				return false;
			}
			
			//	Check for modifiers
			switch( params.last() )
			{
				case 'bok'	:	//	bok requested
					params = params.reduce( 1, 1 );
					elem.bok = true;
					break;
				case 'if'	:	//	Conditional validation requested
					params = params.reduce( 1, 1 );
					elem._if_ = true;
					break;
				case 'then'	:	//	Conditional validation requested
					params = params.reduce( 1, 1 );
					elem._then_ = true;
					break;
				default		:	//	No modifiers
					params = params.reduce( 1, 0 );
				
			}

			/*
			//	Bok requested?
			if ( params.last() == 'bok' )
			{
				//	Trim type and bok from params
				params = params.reduce( 1, 1 );
				elem.bok = true;
			}
			else
			{
				//	Trim bok from params
				params = params.reduce( 1, 0 );
			}
			*/

			//	Is element an array?
			if ( /radio|checkbox/.test( elem.type ) )
			{
				//	Set group property
				elem.group = f.elements[elem.name];
			}
			
			//	Add events if not already added
			if ( typeof elem.fName == 'undefined' )
			{
				//	If element is an array			
				if ( typeof elem.group != 'undefined' )
				{
					for ( var j = 0; j < elem.group.length; j++ )				
					{
						//	Apply event-function to each child
						if ( fv.config.clearEvent != null )
						{
							addEvent( elem.group.item( j ), fv.config.clearEvent, fv, 'revertError', false );
						}
					}
				}
				else
				{
					//	Apply event-function to element
					addEvent( elem, fv.config.clearEvent, fv, 'revertError', false );
				}
			}
			
			//	Set formatted name, current element
			elem.fName    = elem.name.format();
			fv.elem       = elem;

			//	Create function to call the proper validator method of the fValidate class
			var func = new Function( "obj", "method", "obj[method]( " + params.toArgString() + " );" );
			func( fv, type );
		
			//	If element test failed AND group error is off, return false
			if ( elem.validated == false && groupError == false ) return false;
			
			//	Clear error if field okay
			if ( elem.validated == true ) fv.revertError();
		}
	} //	end of element loop
	
	//	If group error, show it
	if ( groupError ) fv.showGroupError();

	//	Return false if errors found
	if ( fv.errors.length > 0 ) return false;

	//	Show pre-submission confirmation
	if ( bConfirm && !confirm( fv.config.confirmMsg ) )
	{
		if ( fv.config.confirmAbortMsg != '' ) alert( fv.config.confirmAbortMsg );
		return false;
	}
	
	//	Disable reset and/or submit buttons if requested
	if ( bDisable ) 
	{
		if ( typeof fv.config.submitButton == 'object' )
		{
			for ( var sb, j = 0; ( sb = fv.config.submitButton[j] ); j++ )
			{
				if ( fv.elementExists( sb ) )
				{
					f.elements[sb].disabled = true;
				}
			}
		} else if ( fv.elementExists( fv.config.submitButton ) )
		{
			f.elements[fv.config.submitButton].disabled = true;
		}
	}
	if ( bDisableR && fv.elementExists( fv.config.resetButton ) )
	{
		f.elements[fv.config.resetButton].disabled = true;
	}

	//alert( 'Success!\n\nRemember, this is just a testing page!' );
	//	Form validated and all options exercised - allow submission
	//return false;
	
	function addEvent( elem, evt, obj, method, capture )
	{
		var self = elem;
		if ( typeof elem.attachEvent != 'undefined' )
		{
			elem.attachEvent( "on" + evt, function() { obj[method]( self ) } );
		}
		else if ( typeof elem.addEventListener != 'undefined' )
		{
			elem.addEventListener( evt, function() { obj[method]( self ) }, capture );
		}
		else if ( fv.config.eventOverride )
		{
			eleme['on' + evt] = function() { obj[method]( self ) };
		}
	}

}

//	Constructor
function fValidate( f, errorMode, groupError )
{
	var self        = this;
	this.form       = f;
	this.errorMode  = errorMode;
	this.groupError = groupError;
	this.errors     = new Array();
	this.validated  = true;
	this.config     = new fValConfig();
	
	//	Add reset action to clear visual error cues
	f.onreset = function()
	{
		var elem, i = 0;
		while ( elem = this.elements[i++] )
		{
			self.revertError( elem );
		}
	}
	
	addLabelProperties();
	
	//	Parses form and adds label properties to elements that have one specified
	function addLabelProperties()
	{
		//	Collect all label elements in form, init vars		
		if ( typeof f.getElementsByTagName == 'undefined' ) return;
		var labels = f.getElementsByTagName( "label" );
		var label, i = j = 0;
		var elem;

		//	Loop through labels retrieved
		while ( label = labels[i++] )
		{
			//	For Opera 6
			if ( typeof label.htmlFor == 'undefined' ) return;
			
			//	Retrieve element
			elem = f.elements[label.htmlFor];
			if ( typeof elem == 'undefined' )
			{	//	No element found for label
				self.devError( "No element found for label: " + label.htmlFor );
			}
			else if ( typeof elem.label != 'undefined' )
			{	//	label property already added
				continue;
			}
			else if ( typeof elem.length != 'undefined' && elem.length > 1 && elem.nodeName != 'SELECT' )
			{	//	For arrayed elements
				for ( j = 0; j < elem.length; j++ )
				{
					elem.item( j ).label = label;
				}
			}
			//	Regular label
			elem.label = label;
		}
	}		
}

//	Checks if element exists in form
fValidate.prototype.elementExists = function( elemName )
{
	return Boolean( typeof this.form.elements[elemName] != 'undefined' );
}

//	Receives error message and determines action
fValidate.prototype.throwError = function( error )
{
	var elem  = this.elem;
	
	//	Arrayed element?
	if ( typeof elem.name == 'undefined' )
	{
		elem = elem[0];
	}

	//	Bok requested AND element blank OR conditional validatoin?
	if ( elem.bok && this.isBlank() )
	{	//	skip		
		elem.validated = true;
		return;
	}

	//	Part of a conditional validation?
	if ( elem.cv )
	{
		return;
	}
	
	//	Retrieve error message, set failsafe to false
	var emsg = ( elem.getAttribute( this.config.emsg ) ) ? elem.getAttribute( this.config.emsg ) : error;
	elem.validated = false;
	
	//	Group error mode?
	if ( this.groupError )
	{
		//	Push error onto stack
		this.errors.push( {'elem':elem, 'msg': error} );		
	}
	else
	{
		//	Process error message		
		this.showError( emsg );

		var focusElem = ( elem.type == 'hidden' )?
			elem.fields[0]:
			elem;
		
		//	Focus and select elements, if possible
		this.selectFocus( focusElem );
	}
}

//	Shows error message to user
fValidate.prototype.showError = function( emsg, last, elem )
{
	//	Set variables
	var self     = this,
		elem     = this.setArg( elem, this.elem ),
		isHidden = Boolean( elem.type == 'hidden' ),
		label    = ( isHidden ) ? null : elem.label || null;

	if ( typeof this.showErrors == 'undefined' ) this.showErrors = new Array();	
	
	//	Determine which error modes to use
	switch( this.errorMode )
	{	//	This represents all possible combinations
		case 0  : alertError(); break;
		case 1  : inputError(); break;
		case 2  : labelError(); break;
		case 3  : appendError(); break;
		case 4  : boxError(); break;
		case 5  : inputError(); labelError(); break;
		case 6  : inputError(); appendError(); break;
		case 7  : inputError(); boxError(); break;
		case 8  : inputError(); alertError(); break;
		case 9  : labelError(); appendError(); break;
		case 10 : labelError(); boxError(); break;
		case 11 : labelError(); alertError(); break;
		case 12 : appendError(); boxError(); break;
		case 13 : appendError(); alertError(); break;
		case 14 : boxError(); alertError(); break;
		case 15 : inputError(); labelError(); appendError(); break;
		case 16 : inputError(); labelError(); boxError(); break;
		case 17 : inputError(); labelError(); alertError(); break;
		case 18 : inputError(); appendError(); boxError(); break;
		case 19 : inputError(); appendError(); alertError(); break;
		case 20 : inputError(); boxError(); alertError(); break;
		case 21 : labelError(); appendError(); boxError(); break;
		case 22 : labelError(); appendError(); alertError(); break;
		case 23 : appendError(); boxError(); alertError(); break;
		case 24 : inputError(); labelError(); appendError(); boxError(); break;
		case 25 : inputError(); labelError(); appendError(); alertError(); break;
		case 26 : inputError(); appendError(); boxError(); alertError(); break;
		case 27 : labelError(); appendError(); boxError(); alertError(); break;
		case 28 : inputError(); labelError(); appendError(); boxError(); alertError(); break;
	}
	//	Regular alert error
	function alertError()
	{
		if ( self.groupError ) self.showErrors.push( emsg );
		else alert( emsg );
		if ( last ) alert( "The following fields had errors\n\n" + self.showErrors.join( "\n\n" ) );			
	}
	//	Applies class to form element
	function inputError()
	{
		if ( ( typeof elem.length != 'undefined' && elem.length > 1 && elem.nodeName != 'SELECT' ) || isHidden )
		{
			var subelem, i = 0;
			while( subelem = ( isHidden ) ? elem.fields[i++] : elem.item( i++ ) )			
			{
				if ( subelem.className != '' )
				{
					subelem.revertClass = subelem.className;
				}
				subelem.className = self.config.errorClass;
			}
		}
		else
		{
			elem.className = self.config.errorClass;
		}
	}
	//	Applies class to element's label
	function labelError()
	{
		if ( label == null ) return;
		label.className = self.config.errorClass;
	}
	//	Appends error message to element's label
	function appendError()
	{
		if ( label == null || typeof label.innerHTML == 'undefined' ) return;
		if ( typeof label.original == 'undefined' )
			label.original = label.innerHTML;
		label.innerHTML = label.original + " - " + emsg.toHTML();
	}
	//	Appends Error message to pre-defined element
	function boxError()
	{
		if ( typeof self.boxError == 'undefined' ) self.boxError = document.getElementById( self.config.boxError );
		if ( typeof self.boxError == 'undefined' )
		{
			this.devError( "A element with the requested id, " + self.config.boxError + ", was not found." );
			return;
		}
		var errorId = self.config.boxErrorPrefix + self.elem.name,
			errorElem;
		if ( errorElem = document.getElementById( errorId ) )
		{
			errorElem.firstChild.nodeValue = emsg.toHTML();
		}
		else
		{
			errorElem = document.createHTMLElement( 'li', { id: errorId, 'innerHTML': emsg.toHTML(), title: 'Click to target field' } );
			errorElem.style.cursor = 'pointer';
			errorElem.onclick = function()
			{
				var elem = self.form.elements[this.id.replace( self.config.boxErrorPrefix, "" )];
				if ( typeof elem.fields != 'undefined' ) elem = elem.fields[0];
					
				if ( typeof elem.select != 'undefined' ) elem.select();
				if ( typeof elem.focus != 'undefined' ) elem.focus();
			}
			self.boxError.appendChild( errorElem );
		}
		self.boxError.style.display = "block";
	}
}

//	Processes errors in stack for group error mode
fValidate.prototype.showGroupError = function()
{
	for ( var error, firstElem, i = 0; ( error = this.errors[i] ); i++ )
	{
		if ( i == 0 ) firstElem = error.elem;
		this.elem = error.elem;
		this.showError( error.msg, Boolean( i == ( this.errors.length - 1 ) ) );
	}
	this.selectFocus( firstElem );
}

//	Reverts any visible error notification upon event
fValidate.prototype.revertError = function( elem )
{
	var errorElem, subelem, i = 0;	
	elem = this.setArg( elem, this.elem );
	var isHidden = Boolean( elem.type == 'hidden' );

	if ( ( typeof elem.length != 'undefined' && elem.length > 1 && elem.nodeName != 'SELECT' ) || isHidden )
	{
		if ( isHidden && typeof elem.fields != 'undefined' )
		{		
			while( subelem = ( isHidden ) ? elem.fields[i++] : elem.item( i++ ) )		
			{
				subelem.className = subelem.revertClass || '';			
			}
		}
	}
	else
	{
		elem.className = elem.revertClass || '';
	}
	if ( typeof elem.label != 'undefined' )
	{
		elem.label.className = '';
		elem.label.innerHTML = ( elem.label.original || elem.label.innerHTML );
	}
	if ( typeof this.boxError != 'undefined' )
	{
		if ( typeof this.boxError.normalize != 'undefined' ) this.boxError.normalize();
		if ( errorElem = document.getElementById( this.config.boxErrorPrefix + elem.name ) )
		{
			this.boxError.removeChild( errorElem );
		}
		if ( this.boxError.childNodes.length == 1 ) this.boxError.style.display = "none";
	}
}

//	Adds error to error stack - group error mode
fValidate.prototype.addError = function( error )
{
	this.errors.push( {'elem':this.elem, 'msg': error} );
}

//	Focus and select elements, if possible
fValidate.prototype.selectFocus = function( elem )
{
	if ( typeof elem.select != 'undefined' ) elem.select();
	if ( typeof elem.focus != 'undefined' )  elem.focus();
}

//	Developer assistance method - shows error if validator/element-type mismatch
fValidate.prototype.typeMismatch = function( type )
{
	var pats = {
		'text': 'text|password|textarea',
		'ta': 'textarea',
		'hidden': 'hidden',
		's1': 'select-one',
		'sm': 'select-multiple',
		'select': 'select-one|select-multiple',
		'rg': 'radio',
		'cb': 'checkbox',
		'file': 'file'
		};
	if ( ! new RegExp( pats[type] ).test( this.elem.type ) )
	{
		this.devError( "Validator/Element type mismatch.\n\nElement: " + this.elem.fName + "\nElement type: " + this.elem.type + "\nType required by validator: " + pats[type].replace( /\|/g, " or " ) );
		this.elem.validated = false;
		return true;
	}
	return false;
}

//	Generic argument setting method
fValidate.prototype.setArg = function( arg, def )
{
	return ( typeof arg == 'undefined' || arg == '' || arg == null ) ? def : arg;
}

//	Blank checker.  Optional string argument for evaluating element other than current
fValidate.prototype.isBlank = function( el )
{
	elem = this.form.elements[el] || this.elem;
	return Boolean( /^\s*$/.test( elem.value ) );
}

//	Throws developer errors
fValidate.prototype.devError = function( msg )
{
	var a = [
		'! WARNING ! -- fValidate developer-assist error\n',
		'----------------------------------------------------------------------------------------------',
		msg,
		'----------------------------------------------------------------------------------------------',
		'\nIf you are not the developer, please contact the website administrator regarding this error.'
		];
	alert( a.join( "\n" ) );
}
/* Non-fValidate methods *****************************************/

//	For easy creation of DOM nodes
document.createHTMLElement = function( elemName, attribs )
{
	if ( typeof document.createElement == 'undefined' ) return;
	var elem = document.createElement( elemName );
	if ( typeof attribs != 'undefined' )
	{
		for ( var i in attribs )
		{
			switch ( true )
			{
				case ( i == 'text' )  : elem.appendChild( document.createTextNode( attribs[i] ) ); break;
				case ( i == 'class' ) : elem.className = attribs[i]; break;
				default : elem.setAttribute( i, '' ); elem[i] = attribs[i];
			}
		}
	}
	return elem;    
}

//	Trims b items from the beginning of the array, e items from the end
Array.prototype.reduce = function( b, e )
{
	var a = new Array();
	var count = 0;
	for ( var i = b; i < this.length - e; i++ )
	{
		a[count++] = this[i];
	}
	return a;
}

//	Returns array as argument-compatible string
Array.prototype.toArgString = function()
{
	var a = new Array();
	for ( var i = 0; ( arg = this[i] ); i++ )
	{
		a.push( "'" + arg + "'" );
	}	
	return a.toString();
}

//	Prototype push if missing
if ( typeof Array.push == 'undefined' )
Array.prototype.push = function()
{
	var arg, i = 0;
	while( arg = arguments[i++] )
	{
		this[this.length] = arg;
	}
	return this.length;
}

//	Returns last item of the array
Array.prototype.last = function()
{
	return this[this.length-1];
}

//	Removes the follow charaters _[] from an elements name for human-reading
String.prototype.format = function()
{
	return this.replace( /\_/g, " ").replace( /\[|\]/g, "" );
}

//	Replaces newline characters with XHTML BR tags
String.prototype.toHTML = function()
{
	return this.replace( /\n/g, "<br />" ).replace( /\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;" );
}

//	Escapes necessary charactes for string-generated regular expressions
String.prototype.toPattern = function()
{
	return this.replace( /([\.\*\+\{\}\(\)\<\>\^\$\\])/g, "\\$1" );
}
//	EOF