/** 
* Copyright 2005-2006 massimocorner.com
* Copyright 2007-2009 tls.net
* @author      Massimo Foti (massimo@massimocorner.com)
* @author      Fred Wittekind (flw@tls.net)
* @version     0.0.16, 2009-04-17
*
* Originally derived from script_tmt_validator.js, although
* very little of the original code still exists.

0.0.13:  Added autoadvance feature
0.0.14:  FCKEditor paste bug fix
0.0.15:  Safari bug fix.  Added RescanEnabled flag to block resursive rescans.
0.0.16:  Minor IE bugfix
**/
var PrototypeValidator = {
	Version: '0.0.16',
	NameSpace: 'tls',
	debug:		false,
	REQUIRED_PROTOTYPE: '1.6.0',
	RescanEnabled: true,
	
	FormDefaultOptions: {
		validate : false,
		blocksubmit : true,
		callback : null,
		errorClass : null,
		errorDisplay : true,
		errorInLabel : false,
		errorMsgClass : null,
		realtime : false
	},
	
	FormElementDefaultOptions: {
		required : false,
		message : null,
		errorclass : null,
		errorInLabel : false,
		errorMsgClass : null,
		pattern : null,
		filters : null,
		minlength : null,
		maxlength : null,
		minnumber : null,
		maxnumber : null,
		datepattern : null,
		equalto : null,
		minchecked : null,
		maxchecked : null,
		invalidindex : null,
		invalidvalue : null,
		waitmessage : null,
		ajaxvalidator : null,
		creditcard : null,
		mindate : null,
		date_day : null,
		date_month : null,
		date_year : null,
		autoadvance : null
	},
	
	load: function() {
		function convertVersionString(versionString){
			var r = versionString.split('.');
			return parseInt(r[0])*100000 + parseInt(r[1])*1000 + parseInt(r[2]);
		}
		
		function UpdateWrapper() {
			var args = $A(arguments);
			var proceed = args.shift();
			var element = $(args[0]);
			var retval = proceed.apply(window, args);
			if (PrototypeValidator.RescanEnabled) {
				if (typeof element.ValidatorLoad != 'undefined')
					element.ValidatorLoad.bind(element).defer();
				element.ValidatorRescan.bind(element).defer();
			}
			return retval;
		}
		
		if ((typeof Prototype=='undefined') || 
				(typeof Element == 'undefined') || 
				(typeof Element.Methods=='undefined') ||
				(convertVersionString(Prototype.Version) < 
				convertVersionString(this.REQUIRED_PROTOTYPE)))
			throw("PrototypeValidator requires the Prototype JavaScript framework >= " +
				this.REQUIRED_PROTOTYPE);
			
		Event.observe(window, 'load', this.EventListeners.pageload);
		
		// Wrap some prototype functions, so we can pick up on updates to the
		// html document.
		// This section requires wrap function added in Prototype 1.5.2
		Element.Methods.update = Element.Methods.update.wrap(UpdateWrapper.bind(this));
		Element.Methods.replace = Element.Methods.replace.wrap(UpdateWrapper.bind(this));
		if (typeof Element.Methods.insert !=  'undefined')
			Element.Methods.insert = Element.Methods.insert.wrap(UpdateWrapper.bind(this));
		if (typeof Element.Methods.addrow !=  'undefined')
			Element.Methods.addrow = Element.Methods.addrow.wrap(UpdateWrapper.bind(this));
		if (typeof Element.Methods.writeAttribute != 'undefined')
			Element.Methods.writeAttribute = Element.Methods.writeAttribute.wrap(UpdateWrapper.bind(this));
		
		// Load our DOM extensions
		Form.Methods.ValidatorGetAttribute = this.ValidatorGetAttribute.bind(this);
		Form.Methods.ValidatorLoad = this.FormValidatorLoad.bind(this);
		Form.Methods.ValidatorValidate = this.formValidator.bind(this);
		Form.Element.Methods.ValidatorGetAttribute = this.ValidatorGetAttribute.bind(this);
		Form.Element.Methods.ValidatorLoad = this.ElementValidatorLoad.bind(this);
		Form.Element.Methods.ValidatorFlagInvalid = this.FlagInvalid.bind(this);
		Form.Element.Methods.ValidatorFlagValid = this.FlagValid.bind(this);
		Form.Element.Methods.ValidatorValidate = this.Validate.bind(this);
		Form.Element.Methods.ValidatorFilter = this.filterField.bind(this);
		Form.Element.Methods.ValidatorAutoAdvance = this.AutoAdvanceField.bind(this);
		Form.Element.Methods.ValidatorNextField = this.NextField.bind(this);
		Form.Element.Methods.toJSON = this.FormElementToJSON.bind(this);
		Element.Methods.ValidatorRescan = this.ValidatorRescan.bind(this);
		Element.addMethods();
		if (this.debug) alert('Prototype validator loaded');
	},
	
	EventListeners : {
		pageload : function(e) {
			$$('form').invoke('ValidatorLoad');
		},
		formValidator: function(e) {
			var element = $(Event.element(e));
			element.ValidatorValidate();
			if (!element.ValidatorIsValid)
				Event.stop(e);
		},
		filterField: function(e) {
			var element =  $(Event.element(e));
			element.ValidatorFilter();
		},
		autoadvField: function(e) {
			function afterpaste(element) {
				if (element.ValidatorOptions.filters != null)
					element.ValidatorFilter();
				element.ValidatorAutoAdvance();
			}
			
			var element =  $(Event.element(e));
			switch (e.type) {
				case 'keyup' :
					if (element.ValidatorOptions.filters != null)
						element.ValidatorFilter();
					var keyCode = e.keyCode;
					var filter = $A([0,8,9,16,17,18,37,38,39,40,46]);
					if ((keyCode) && (filter.indexOf(keyCode) == -1))
						element.ValidatorAutoAdvance();
					break;
				case 'paste' :
					afterpaste.defer(element);
					break;
				case 'blur' :
					if (element.ValidatorOptions.filters != null)
						element.ValidatorFilter();
					element.ValidatorAutoAdvance();
					break;
			}
		},
		realtime: function(e) {
			var element =  $(Event.element(e));
			var formNode = $(element.form);
			if (formNode.ValidatorOptions.realtime) {
				if ($(element).FCKeditorInstance) {
					var editorInstance = $(element).FCKeditorInstance;
					editorInstance.UpdateLinkedField();
				}
				element.ValidatorValidate();
			}
		}
	},
	
	FormElementToJSON : function(element) {
		function getattrbyidx(element, idx) {
			var retval = null
			try {
				retval = element.attributes[idx];
			} catch(e) {}
			return retval;
		}
	
		element = $(element);
		var retval = new Hash();
	
		var i = 0,a,nodeName;
		while (a = getattrbyidx(element, i)) {
			i = i + 1;
			if (a.specified) {
				nodeName = a.nodeName;
				if ((!Element[nodeName]) && (!Form.Element.Methods[nodeName]) && (nodeName != '_extendedByPrototype')) {
					retval.set(nodeName, element.readAttribute(nodeName) );
				}
			}
		}
	
		retval.set('value', element.getValue() );
		retval.set('tagName', element.tagName.toLowerCase() );
		switch (retval.tagName) {
			case 'input' :
				retval.set('type', element.type.toLowerCase() );
				break;
			case 'textarea' :
				break;
			case 'select' :
				retval.set('multiple', element.multiple);
				retval.set('selectedIndex', element.selectedIndex);
				break;
		}
		retval.set('ValidatorOptions', element.ValidatorOptions);
		return $H(retval).toJSON();
	},
	
	ValidatorGetAttribute : function(element, attribute) {
		var retval = null;
		if ((element.tagName.toLowerCase() == 'input') && (['radio','checkbox'].include(element.type))) {
			var group = $(element.form).getInputs(element.type, element.name);
			var retval;
			for (var i=0; i < group.length; i++) {
				if ($(group[i]).readAttribute(this.NameSpace + ':' + attribute)) {
					retval = $(group[i]).readAttribute(this.NameSpace + ':' + attribute);
				}
			}
		} else {
			retval = $(element).readAttribute(this.NameSpace + ':' + attribute);
		}
		if (retval == null)
			if ($(element).ValidatorOptions != undefined)
				if ($(element).ValidatorOptions[attribute] != undefined)
					retval = $(element).ValidatorOptions[attribute];
		if (retval == '') retval = null;
		if (retval == 'false') retval = false;
		return retval;
	},
	
	FormValidatorLoad : function(formNode) {
		if (this.debug) alert('FormValidatorLoad:' + formNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		if ($(formNode).ValidatorOptions == undefined) 
			$(formNode).ValidatorOptions = Object.clone(this.FormDefaultOptions);
		
		var vokey;
		var vokeys = $H(this.FormDefaultOptions).keys();
		for (var i=0; i < vokeys.length; i++) {
			vokey = vokeys[i];
			$(formNode).ValidatorOptions[vokey] = $(formNode).ValidatorGetAttribute(vokey);
		}
		
		Event.stopObserving(formNode, 'submit', this.EventListeners.formValidator);
		if ($(formNode).ValidatorOptions.validate) {
			$(formNode).observe('submit', this.EventListeners.formValidator);
			
			// Trigger ValidatorLoad on form elements.
			$(formNode).select('input','select','textarea').invoke('ValidatorLoad');
		}
		this.RescanEnabled = saveRescanEnabled;
		return formNode;
	},
	
	ValidatorRescan : function(element) {
		if (this.debug) alert('ValidatorRescan:' + element.id);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		element = $(element);
		// Find parent form tag, if there is one
		var parent_form = null;
		element.ancestors().each(function (ancestor) {
			if (ancestor.tagName.toLowerCase() == "form")
				parent_form = $(ancestor);
		});
		if (parent_form != null) {
			if (this.debug) alert('ValidatorRescan - has parent form, so scanning for form fields');
			if (parent_form.ValidatorOptions != undefined)
				if (parent_form.ValidatorOptions.validate)
					$A(element.select('input','select','textarea')).invoke('ValidatorLoad');
		} else {
			if (this.debug) alert('ValidatorRescan - no parent form, so checking for form tags');
			$A(element.select('form')).invoke('ValidatorLoad');
		}
		this.RescanEnabled = saveRescanEnabled;
	},
	
	ElementValidatorLoad : function(fieldNode) {
		if (this.debug) alert('ElementValidatorLoad:' + fieldNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		formNode = fieldNode.form;
		if ($(fieldNode).ValidatorOptions == undefined) 
			$(fieldNode).ValidatorOptions = Object.clone(this.FormElementDefaultOptions);
		
		var vokey;
		var vokeys = $H(this.FormElementDefaultOptions).keys();
		for (var i=0; i < vokeys.length; i++) {
			vokey = vokeys[i];
			$(fieldNode).ValidatorOptions[vokey] = $(fieldNode).ValidatorGetAttribute(vokey);
		}
		

		// Remove existing event handlers
		$(fieldNode).stopObserving('keyup', this.EventListeners.filterField);
		$(fieldNode).stopObserving('blur', this.EventListeners.filterField);
		$(fieldNode).stopObserving('keyup', this.EventListeners.autoadvField);
		$(fieldNode).stopObserving('blur', this.EventListeners.autoadvField);
		$(fieldNode).stopObserving('paste', this.EventListeners.autoadvField);
		$(fieldNode).stopObserving('blur', this.EventListeners.realtime);
		$(fieldNode).stopObserving('FCKeditor:Blur', this.EventListeners.realtime);
		
		// Initialize our event handlers
		if ($(fieldNode).ValidatorOptions.autoadvance != null) {
			$(fieldNode).observe('keyup', this.EventListeners.autoadvField);
			$(fieldNode).observe('blur', this.EventListeners.autoadvField);
			$(fieldNode).observe('paste', this.EventListeners.autoadvField);
		} else {
			// Auto Advance code has to have field filtered first so
			// it will manually call filter to ensure order of operations.
			if ($(fieldNode).ValidatorOptions.filters != null) {
				$(fieldNode).observe('keyup', this.EventListeners.filterField);
				$(fieldNode).observe('blur', this.EventListeners.filterField);
			}
		}
		if ($(formNode).ValidatorOptions.realtime) {
			$(fieldNode).observe('blur', this.EventListeners.realtime);
			$(fieldNode).observe('FCKeditor:Blur', this.EventListeners.realtime);
			$(fieldNode).ValidatorValidate();
		}
		this.RescanEnabled = saveRescanEnabled;
		return fieldNode;
	},
	
	FlagInvalid : function(fieldNode) {
		if (this.debug) alert('FlagInvalid:' + fieldNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		var errorClass = null;
		var errorInLabel = null;
		var errorMsgClass = null;
		var errorMsg;
		
		if ($(fieldNode.form).ValidatorOptions.errorClass != null)
			errorClass = $(fieldNode.form).ValidatorOptions.errorClass;
		if ($(fieldNode.form).ValidatorOptions.errorInLabel)
			errorInLabel = $(fieldNode.form).ValidatorOptions.errorInLabel;
		if ($(fieldNode.form).ValidatorOptions.errorMsgClass != null)
			errorMsgClass = $(fieldNode.form).ValidatorOptions.errorMsgClass;

		if ($(fieldNode).ValidatorOptions.errorClass != null)
			errorClass = $(fieldNode).ValidatorOptions.errorClass;
		if ($(fieldNode).ValidatorOptions.errorInLabel)
			errorInLabel = $(fieldNode).ValidatorOptions.errorInLabel;
		if ($(fieldNode).ValidatorOptions.errorMsgClass != null)
			errorMsgClass = $(fieldNode).ValidatorOptions.errorMsgClass;
			
		var label = $$('label[for="' + $(fieldNode).identify() + '"]').first();
		if (errorClass != null) {
			$(fieldNode).addClassName(errorClass);
			if ($(label)) {
				$(label).addClassName(errorClass)
				if (errorInLabel && errorMsgClass) {
					errorMsg = $(fieldNode).ValidatorOptions.message;
					if ($(fieldNode).ValidatorAjaxResult)
						if (!$(element).ValidatorAjaxResult.isvalid) {
							errorMsg = errorMsg + '<ul>';
							for (var a=0; a < $(element).ValidatorAjaxResult.errors.length; a++) {
								errorMsg = errorMsg + '<li>';
								errorMsg = errorMsg + $(element).ValidatorAjaxResult.errors[a];
								errorMsg = errorMsg + '</li>';
							}
							errorMsg = errorMsg + '</ul>';
						}
					var errorMsgNode = label.select('.' + errorMsgClass).first();
					if (errorMsgNode) errorMsgNode.update(errorMsg);
				}
			}
		}
		$(fieldNode).ValidatorIsValid = false;
		this.RescanEnabled = saveRescanEnabled;
		return fieldNode;
	},
	
	FlagValid : function(fieldNode) {
		if (this.debug) alert('FlagValid:' + fieldNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		var errorClass = null;
		var errorInLabel = null;
		var errorMsgClass = null;

		if ($(fieldNode.form).ValidatorOptions.errorClass != null)
			errorClass = $(fieldNode.form).ValidatorOptions.errorClass;
		if ($(fieldNode.form).ValidatorOptions.errorInLabel)
			errorInLabel = $(fieldNode.form).ValidatorOptions.errorInLabel;
		if ($(fieldNode.form).ValidatorOptions.errorMsgClass != null)
			errorMsgClass = $(fieldNode.form).ValidatorOptions.errorMsgClass;

		if ($(fieldNode).ValidatorOptions.errorClass != null)
			errorClass = $(fieldNode).ValidatorOptions.errorClass;
		if ($(fieldNode).ValidatorOptions.errorInLabel)
			errorInLabel = $(fieldNode).ValidatorOptions.errorInLabel;
		if ($(fieldNode).ValidatorOptions.errorMsgClass != null)
			errorMsgClass = $(fieldNode).ValidatorOptions.errorMsgClass;
			
		var label = $$('label[for="' + $(fieldNode).identify() + '"]').first();
		if (errorClass != null) {
			$(fieldNode).removeClassName(errorClass);
			if ($(label)) {
				$(label).removeClassName(errorClass);
				if (errorInLabel && errorMsgClass) {
					var errorMsgNode = label.select('.' + errorMsgClass).first();
					if (errorMsgNode) errorMsgNode.update('');
				}
			}
		}
		$(fieldNode).ValidatorIsValid = true;
		this.RescanEnabled = saveRescanEnabled;
		return fieldNode;
	},
	
	Validate : function(fieldNode) {
		if (this.debug) alert('Validate:' + fieldNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		if (fieldNode.disabled) {
			$(fieldNode).ValidatorFlagValid();
		} else {
			if (this.abstractValidator(fieldNode)) {
				$(fieldNode).ValidatorFlagValid();
			} else {
				$(fieldNode).ValidatorFlagInvalid();
			}
		}
		this.RescanEnabled = saveRescanEnabled;
		return fieldNode;
	},
	
	formValidator : function(formNode) {
		if (this.debug) alert('formValidator:' + formNode.name);
		var saveRescanEnabled = this.RescanEnabled;
		this.RescanEnabled = false;
		
		// Add compatiblity with FCKEditor
		if (window.FCKeditorAPI) {
			$H(window.FCKeditorAPI.Instances).each(function(pair) {
				var instance = pair.key;
				var fck = pair.value;
				fck.UpdateLinkedField();
			});
		}
		
		var elements = $(formNode).select('input','select','textarea');
		var element;
		var error_elements = $H({}); // Hash to contain
		//var invalid = $H({});
		var invalid = new Hash();
		var errorMsg = "";
		elements.invoke('ValidatorValidate');
		$(formNode).ValidatorIsValid = true;
		for (var i=0; i < elements.length; i++) {
			element = $(elements[i]);
			if (!element.ValidatorIsValid) {
				$(formNode).ValidatorIsValid = false;
				if (invalid.get(element.name) == undefined) {
					invalid.set(element.name, element);
					if ($(element).ValidatorOptions.message)
						errorMsg = errorMsg + $(element).ValidatorOptions.message + "\n";
					if ($(element).ValidatorAjaxResult)
						if (!$(element).ValidatorAjaxResult.isvalid)
							for (var a=0; a < $(element).ValidatorAjaxResult.errors.length; a++) {
								errorMsg = errorMsg + '     ' + $(element).ValidatorAjaxResult.errors[a] + "\n";
							}
				}
			}
		}
		if ($(formNode).ValidatorIsValid) {
			if ($(formNode).ValidatorOptions.blocksubmit) {
				for (var i=0; i < elements.length; i++) {
					element = $(elements[i]);
					if (element.tagName.toLowerCase() == 'input')
						if (element.type.toLowerCase() == 'submit') {
							element.disabled = true;
							if (element.ValidatorOptions.waitmessage) {
								element.ValidatorOptions.originalvalue = element.value;
								element.value = element.ValidatorOptions.waitmessage;
							}
						}
				}
			}
		} else {
			if ($(formNode).ValidatorOptions.callback) {
				if (this.debug) alert('Calling validator callback');
				eval($(formNode).ValidatorOptions.callback + "(formNode, invalid, errorMsg);");
			} else {
				if ($(formNode).ValidatorOptions.errorDisplay) {
					alert(errorMsg);
				} else {
					if (this.debug) alert('Error display surpressed');
				}
			}
		}
		this.RescanEnabled = saveRescanEnabled;
		return formNode;
	},
	
	abstractValidator : function(fieldNode) {
		if (this.debug) alert('abstractValidator:' + fieldNode.name);
		switch (fieldNode.tagName.toLowerCase()) {
			case 'input' :
				switch (fieldNode.type.toLowerCase()) {
					case 'text' :
					case 'hidden' :
						return this.textValidator(fieldNode);
					case 'radio' :
						return this.radioValidator(fieldNode);
					case 'checkbox' :
						return this.boxValidator(fieldNode);
					case 'button' :
					case 'reset' :
					case 'submit' :
						return true;
				}
			case 'textarea' :
				return this.textValidator(fieldNode);
			case 'select' :
				return this.selectValidator(fieldNode);
		}
	},

	textValidator : function(fieldNode) {
		if (this.debug) alert('textValidator:' + fieldNode.name);
		if ($(fieldNode).getValue() == '') {
			if ($(fieldNode).ValidatorAjaxResult)
				$(fieldNode).ValidatorAjaxResult = false;
			if ($(fieldNode).ValidatorOptions.required) {
				return false;
			} else {
				return true;
			}
		}
		for (var rule in this.globalRules)
			if ($(fieldNode).ValidatorOptions[rule])
				if (!this.globalRules[rule](fieldNode))
					return false;
		return true;
	},
	
	selectValidator : function(selectNode) {
		if (this.debug) alert('selectValidator:' + selectNode.name + ' invalidindex = ' + $(selectNode).ValidatorOptions.invalidindex);
		if ($(selectNode).ValidatorOptions.invalidindex != undefined)
			if (selectNode.selectedIndex == $(selectNode).ValidatorOptions.invalidindex)
				return false;
		if ($(selectNode).ValidatorOptions.invalidvalue != undefined)
			if ($(selectNode).getValue() == $(selectNode).ValidatorOptions.invalidvalue)
				return false;
		return true;
	},
	
	boxValidator : function(fieldNode) {
		if (this.debug) alert('boxValidator:' + fieldNode.name);
		var boxGroup = $(fieldNode.form).getInputs(fieldNode.type, fieldNode.name);
		var numchecked = 0;
		for (var i=0; i < boxGroup.length; i++) {
			if (boxGroup[i].checked)
				numchecked = numchecked + 1;
		}
		if ($(fieldNode).ValidatorOptions.minchecked != null)
			if ($(fieldNode).ValidatorOptions.minchecked > numchecked)
				return false;
		if ($(fieldNode).ValidatorOptions.maxchecked != null)
			if ($(fieldNode).ValidatorOptions.maxchecked < numchecked)
				return false;
		return true;
	},
	
	radioValidator : function(fieldNode) {
		if (this.debug) alert('radioValidator:' + fieldNode.name);
		var radioGroup = $(fieldNode.form).getInputs(fieldNode.type, fieldNode.name);
		var numchecked = 0;
		for (var i=0; i < radioGroup.length; i++) {
			if (radioGroup[i].checked)
				numchecked = numchecked + 1;
		}
		if ($(fieldNode).ValidatorOptions.required)
			if (numchecked == 0){
				return false;
			}
		return true;
	
	},
	
	// This global objects store all the validation rules
	// Every rule is stored as a method that accepts the field node as argument and return a boolean
	globalRules : {
		datepattern  : function(fieldNode) {
			var globalObj = PrototypeValidator.DatePatterns[$(fieldNode).ValidatorOptions.datepattern];
			if(globalObj){
				// Split the date into 3 different bits using the separator
				var dateBits = fieldNode.value.split(globalObj.s);
				// First try to create a new date out of the bits
				var testDate = new Date(dateBits[globalObj.y], (dateBits[globalObj.m]-1), dateBits[globalObj.d]);
				// Make sure values match after conversion
				var isDate = (testDate.getFullYear() == dateBits[globalObj.y])
						 && (testDate.getMonth() == dateBits[globalObj.m]-1)
						 && (testDate.getDate() == dateBits[globalObj.d]);
				// If it's a date and it matches the RegExp, it's a go
				return isDate && globalObj.rex.test(fieldNode.value);
			}
		},
		
		equalto  : function(fieldNode) {
			var twinNode = $(fieldNode).ValidatorOptions.equalto;
			return $(twinNode).value == fieldNode.value;
		},
		
		maxlength  : function(fieldNode) {
			if (fieldNode.value.length > $(fieldNode).ValidatorOptions.maxlength) {
				return false;
			}
			return true;
		},
		
		maxnumber  : function(fieldNode) {
			if (parseFloat(fieldNode.value) > $(fieldNode).ValidatorOptions.maxnumber) {
				return false;
			}
			return true;
		},
		
		minlength  : function(fieldNode) {
			if (fieldNode.value.length < $(fieldNode).ValidatorOptions.minlength) {
				return false;
			}
			return true;
		},
		
		minnumber  : function(fieldNode) {
			if (parseFloat(fieldNode.value) < $(fieldNode).ValidatorOptions.minnumber) {
				return false;
			}
			return true;
		},
		
		pattern  : function(fieldNode) {
			var reg = PrototypeValidator.Patterns[$(fieldNode).ValidatorOptions.pattern];
			if (reg) {
				return reg.test(fieldNode.value);
			} else {
				// If the pattern is missing, skip it
				return true;	
			}
		},
		
		ajaxvalidator : function(fieldNode) {
			function AjaxValidatorReturn(fieldNode, transport, json) {
				fieldNode.ValidatorAjaxResult = json;
			}
			function AjaxValidatorException(request, exception) {
				throw(exception);
			}
			function AjaxValidatorFailure() {
				alert('ajaxvalidator failure');
			}
			fieldNode = $(fieldNode);
			fieldNode.ValidatorAjaxResult = {
				isvalid:		null,
				errors:			null,
				waiting:		true
			};
			new Ajax.Request(fieldNode.ValidatorOptions.ajaxvalidator, {
				method: 		'post',
				asynchronous:	false,
				parameters:		{
					field:	$(fieldNode).toJSON(),
					form:	$H($(fieldNode.form).serialize(true)).toJSON()
				},
				onSuccess:		AjaxValidatorReturn.bind(this, fieldNode),
				onException:	AjaxValidatorException,
				onFailure:		AjaxValidatorFailure
			});
			return fieldNode.ValidatorAjaxResult.isvalid;
		},
		
		creditcard : function(fieldNode) {
			// Credit card validation function derived from code
			// written by John Gardner
			// http://www.braemoor.co.uk/software/creditcard.shtml
			var cards = {
				'Visa' : {
					length: "13,16", 
					prefixes: "4",
					checkdigit: true
				},
				'MasterCard' : {
					length: "16", 
					prefixes: "51,52,53,54,55",
					checkdigit: true
				},
				'DinersClub' : {
					length: "14,16", 
					prefixes: "300,301,302,303,304,305,36,38,55",
					checkdigit: true
				},
				'CarteBlanche' : {
					length: "14", 
					prefixes: "300,301,302,303,304,305,36,38",
					checkdigit: true
				},
				'Amex' : {
					length: "15", 
					prefixes: "34,37",
					checkdigit: true
				},
				'Discover' : {
					length: "16", 
					prefixes: "6011,650",
					checkdigit: true
				},
				'JCB' : {
					length: "15,16", 
					prefixes: "3,1800,2131",
					checkdigit: true
				},
				'enRoute' : {
					length: "15", 
					prefixes: "2014,2149",
					checkdigit: true
				},
				'Solo' : { 
					length: "16,18,19", 
					prefixes: "6334, 6767",
					checkdigit: true
				},
				'Switch' : {
					length: "16,18,19", 
					prefixes: "4903,4905,4911,4936,564182,633110,6333,6759",
					checkdigit: true
				},
				'Maestro' : { 
					length: "16", 
					prefixes: "5020,6",
					checkdigit: true
				},
				'VisaElectron' : { 
					length: "16", 
					prefixes: "417500,4917,4913",
					checkdigit: true
				}
			};
			
			if ($(fieldNode).ValidatorOptions.creditcard == "")
				return true;
			
			// Can't validate card if we don't have the type
			// setting cctype field to required will trap this error correctly.
			var cctype_field = $(fieldNode).ValidatorOptions.creditcard;
			if (cards[$(cctype_field).getValue()] == undefined)
				return true;
			
			var cctype = cards[$(cctype_field).getValue()];
			var cardnumber = fieldNode.value.replace (/\s/g, "");
			
			// Setting this field to required will trap this error correctly.
			// Don't trap it here.
			if (cardnumber.length == 0)
				return true;

			// Check that the number is numeric
			var cardNo = cardnumber;
			var cardexp = /^[0-9]{13,19}$/;
			if (!cardexp.exec(cardNo))
				return false;
			
			// Now check the modulus 10 check digit - if required
			if (cctype.checkdigit) {
				var checksum = 0;	// running checksum total
				var mychar = "";	// next char to process
				var j = 1;	// takes value of 1 or 2
				
				// Process each digit one by one starting at the right
				var calc;
				for (i = cardNo.length - 1; i >= 0; i--) {
					// Extract the next digit and multiply by 1 or 2 on alternative digits.
					calc = Number(cardNo.charAt(i)) * j;
					
					// If the result is in two digits add 1 to the checksum total
					if (calc > 9) {
						checksum = checksum + 1;
						calc = calc - 10;
					}
					
					// Add the units element to the checksum total
					checksum = checksum + calc;
					
					// Switch the value of j
					if (j ==1) {j = 2} else {j = 1};
				} // for (i = cardNo.length - 1; i >= 0; i--)
				
				// All done - if checksum is divisible by 10, it is a valid modulus 10.
				// If not, report an error.
				if (checksum % 10 != 0)
					return false;
			} // if (cctype.checkdigit)
			
			// The following are the card-specific checks we undertake.
			var LengthValid = false;
			var PrefixValid = false;
			var undefined; 
			
			// We use these for holding the valid lengths and prefixes of a card type
			var prefix = new Array ();
			var lengths = new Array ();
			
			// Load an array with the valid prefixes for this card
			prefix = cctype.prefixes.split(",");
			
			// Now see if any of them match what we have in the card number
			for (i=0; i<prefix.length; i++) {
				var exp = new RegExp ("^" + prefix[i]);
				if (exp.test (cardNo)) PrefixValid = true;
			} // for (i=0; i<prefix.length; i++)
			
			// If it isn't a valid prefix there's no point at looking at the length
			if (!PrefixValid) return false;
			
			// See if the length is valid for this card
			lengths = cctype.length.split(",");
			for (j=0; j<lengths.length; j++) {
				if (cardNo.length == lengths[j]) LengthValid = true;
			} // for (j=0; j<lengths.length; j++)
			
			// See if all is OK by seeing if the length was valid. We only check the 
			// length if all else was hunky dory.
			if (!LengthValid) return false;
			return true;
		},
		
		mindate : function(fieldNode) {
			fieldNode = $(fieldNode);
			var d = new Date(fieldNode.ValidatorOptions.mindate);
			var date_month = $(fieldNode.ValidatorOptions.date_month).getValue();
			var date_day = $(fieldNode.ValidatorOptions.date_day).getValue();
			var date_year = $(fieldNode.ValidatorOptions.date_year).getValue();
			if (date_year == "") return true;
			if (d.getYear() > date_year) return false;
			if (date_month == "") return true;
			if (d.getYear() == date_year) {
				if ((d.getMonth()+1) > date_month) return false;
				if ((d.getMonth()+1) == date_month) {
					if (date_day == "") return true;
					if (d.getDate() > date_day) return false;
				}
			}
			return true;
		}
	},
	
	// Create an object that stores date validation's info
	dateInfo : function(rex, year, month, day, separator) {
		var infoObj = new Object;
		infoObj.rex = new RegExp(rex);
		infoObj.y = year;
		infoObj.m = month;
		infoObj.d = day;
		infoObj.s = separator;
		return infoObj;
	},
	
	// Create an object that stores filters's info
	filterInfo : function(rex, replaceStr) {
		var infoObj = new Object;
		infoObj.rex = new RegExp(rex, "g");
		infoObj.str = replaceStr;
		return infoObj;
	},
	
	// Clean up the field based on filter's info
	filterField : function(fieldNode) {
		fieldvalue = fieldNode.value;
		var filtersArray = $(fieldNode).ValidatorOptions.filters.split(",");
		for(var i=0; i<filtersArray.length; i++){
			var filtObj = this.Filters[filtersArray[i]];
			// Be sure we have the filter's data, then clean up
			if(filtObj){
				fieldvalue = fieldvalue.replace(filtObj.rex, filtObj.str)
			}
			// We handle demoroziner as a special case
			if(filtersArray[i] == "demoronizer"){
				fieldvalue = this.filterDemoronizer(fieldvalue);
			}
		}
		if (fieldvalue != fieldNode.value)
			fieldNode.value = fieldvalue;
	},
	
	// Replace MS Word's non-ISO characters with plausible substitutes
	filterDemoronizer : function(str) {
		str = str.replace(new RegExp(String.fromCharCode(710), "g"), "^");
		str = str.replace(new RegExp(String.fromCharCode(732), "g"), "~");
		// Evil "smarty" quotes
		str = str.replace(new RegExp(String.fromCharCode(8216), "g"), "'");
		str = str.replace(new RegExp(String.fromCharCode(8217), "g"), "'");
		str = str.replace(new RegExp(String.fromCharCode(8220), "g"), '"');
		str = str.replace(new RegExp(String.fromCharCode(8221), "g"), '"');
		// More MS Word's garbage
		str = str.replace(new RegExp(String.fromCharCode(8211), "g"), "-");
		str = str.replace(new RegExp(String.fromCharCode(8212), "g"), "--");
		str = str.replace(new RegExp(String.fromCharCode(8218), "g"), ",");
		str = str.replace(new RegExp(String.fromCharCode(8222), "g"), ",,");
		str = str.replace(new RegExp(String.fromCharCode(8226), "g"), "*");
		str = str.replace(new RegExp(String.fromCharCode(8230), "g"), "...");
		str = str.replace(new RegExp(String.fromCharCode(8364), "g"), "€");
		return str;
	},
	
	AutoAdvanceField : function(fieldNode) {
		fieldNode = $(fieldNode);
		if (fieldNode.ValidatorOptions.autoadvance != null) {
			var max_len = parseInt(fieldNode.ValidatorOptions.autoadvance);
			var cur_len = fieldNode.getValue().length;
			if (cur_len >= max_len) {
				var next = fieldNode.ValidatorNextField();
				if (cur_len > max_len) {
					next.value = fieldNode.getValue().substring(max_len);
					fieldNode.value = fieldNode.getValue().truncate(max_len, '');
				}
				if (next) {
					next.focus();
					next.ValidatorAutoAdvance();
				}
			}
		}
		return fieldNode;
	},
	
	NextField : function(fieldNode) {
		fieldNode = $(fieldNode);
		var form = $(fieldNode.form);
		var elements = form.getElements();
		var found = false;
		for (var i=0; i < elements.length; i++) {
			if (found) 
				return elements[i];
			if (fieldNode.identify() == elements[i].identify())
				found = true;
		}
		return null;
	}
};

// This global objects store all the RegExp patterns for strings
PrototypeValidator.Patterns = {
	email : new RegExp("^[\\w\\.=-]+@[\\w\\.-]+\\.[\\w\\.-]{2,4}$"),
	lettersonly : new RegExp("^[a-zA-Z]*$"),
	alphanumeric : new RegExp("^\\w*$"),
	integer : new RegExp("^-?\\d\\d*$"),
	positiveinteger : new RegExp("^\\d\\d*$"),
	number : new RegExp("^-?(\\d\\d*\\.\\d*$)|(^-?\\d\\d*$)|(^-?\\.\\d\\d*$)"),
	url : new RegExp('^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?([^_/:\ ]+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?'),
	filepath_pdf : new RegExp("[\\w_\\-\\ ]*\\.([pP][dD][fF])$"),
	filepath_pdf_txt : new RegExp("[\\w_\\-\\ ]*\\.([pP][dD][fF])|([tT][xX][tT])$"),
	filepath_jpg_gif : new RegExp("[\\w_\\-\\ ]*\\.([gG][iI][fF])|([jJ][pP][eE]?[gG])$"),
	filepath_jpg_gif_swf : new RegExp("[\\w_\\-\\ ]*\\.([gG][iI][fF])|([jJ][pP][eE]?[gG])|([sS][wW][fF])$"),
	filepath_jpg_gif_png : new RegExp("[\\w_\\-\\ ]*\\.([gG][iI][fF])|([jJ][pP][eE]?[gG])|([pP][nN][gG])$"),
	filepath_jpg_gif_png_swf : new RegExp("[\\w_\\-\\ ]*\\.([gG][iI][fF])|([jJ][pP][eE]?[gG])|([sS][wW][fF])|([pP][nN][gG])$"),
	filepath_jpg : new RegExp("[\\w_\\-\\ ]*\\.([jJ][pP][eE]?[gG])$"),
	filepath_gif : new RegExp("[\\w_\\-\\ ]*\\.([gG][iI][fF])$"),
	filepath_png : new RegExp("[\\w_\\-\\ ]*\\.([pP][nN][gG])$"),
	filepath_swf : new RegExp("[\\w_\\-\\ ]*\\.([sS][wW][fF])$"),
	filepath_zip : new RegExp("[\\w_\\-\\ ]*\\.([zZ][iI][pP])$"),
	filepath_txt : new RegExp("[\\w_]*\\.([tT][xX][tT])$"),
	filepath_html : new RegExp("[\\w_]*\\.([hH][tT][mM]?[lL])$"),
	filepath : new RegExp("[\\w_\\-\\ ]*\\.\\w{3}$"),
	// Added by Bill Linder on August 14th, 2007
	phone : new RegExp("^((\\+\\d{1,3}(-| )?\\(?\\d\\)?(-| )?\\d{1,5})|(\\(?\\d{2,6}\\)?))(-| )?(\\d{3,4})(-| )?(\\d{4})(( x| ext)( )?\\d{1,5}){0,1}$")
	//phone_local_us : new RegExp("(1|0)?")
};

// This objects store all the info required for date validation
PrototypeValidator.DatePatterns = {
		"YYYY-MM-DD" : PrototypeValidator.dateInfo("^\([0-9]{4}\)\\-\([0-1][0-9]\)\\-\([0-3][0-9]\)$", 0, 1, 2, "-"),
		"YYYY-M-D" : PrototypeValidator.dateInfo("^\([0-9]{4}\)\\-\([0-1]?[0-9]\)\\-\([0-3]?[0-9]\)$", 0, 1, 2, "-"),
		"MM.DD.YYYY" : PrototypeValidator.dateInfo("^\([0-1][0-9]\)\\.\([0-3][0-9]\)\\.\([0-9]{4}\)$", 2, 0, 1, "."),
		"M.D.YYYY" : PrototypeValidator.dateInfo("^\([0-1]?[0-9]\)\\.\([0-3]?[0-9]\)\\.\([0-9]{4}\)$", 2, 0, 1, "."),
		"MM/DD/YYYY" : PrototypeValidator.dateInfo("^\([0-1][0-9]\)\/\([0-3][0-9]\)\/\([0-9]{4}\)$", 2, 0, 1, "/"),
		"M/D/YYYY" : PrototypeValidator.dateInfo("^\([0-1]?[0-9]\)\/\([0-3]?[0-9]\)\/\([0-9]{4}\)$", 2, 0, 1, "/"),
		"MM-DD-YYYY" : PrototypeValidator.dateInfo("^\([0-21][0-9]\)\\-\([0-3][0-9]\)\\-\([0-9]{4}\)$", 2, 0, 1, "-"),
		"M-D-YYYY" : PrototypeValidator.dateInfo("^\([0-1]?[0-9]\)\\-\([0-3]?[0-9]\)\\-\([0-9]{4}\)$", 2, 0, 1, "-"),
		"DD.MM.YYYY" : PrototypeValidator.dateInfo("^\([0-3][0-9]\)\\.\([0-1][0-9]\)\\.\([0-9]{4}\)$", 2, 1, 0, "."),
		"D.M.YYYY" : PrototypeValidator.dateInfo("^\([0-3]?[0-9]\)\\.\([0-1]?[0-9]\)\\.\([0-9]{4}\)$", 2, 1, 0, "."),
		"DD/MM/YYYY" : PrototypeValidator.dateInfo("^\([0-3][0-9]\)\/\([0-1][0-9]\)\/\([0-9]{4}\)$", 2, 1, 0, "/"),
		"D/M/YYYY" : PrototypeValidator.dateInfo("^\([0-3]?[0-9]\)\/\([0-1]?[0-9]\)\/\([0-9]{4}\)$", 2, 1, 0, "/"),
		"DD-MM-YYYY" : PrototypeValidator.dateInfo("^\([0-3][0-9]\)\\-\([0-1][0-9]\)\\-\([0-9]{4}\)$", 2, 1, 0, "-"),
		"D-M-YYYY" : PrototypeValidator.dateInfo("^\([0-3]?[0-9]\)\\-\([0-1]?[0-9]\)\\-\([0-9]{4}\)$", 2, 1, 0, "-")
};

// This objects store all the info required for filters
PrototypeValidator.Filters = {
	ltrim : PrototypeValidator.filterInfo("^(\\s*)(\\b[\\w\\W]*)$", "$2"),
	rtrim : PrototypeValidator.filterInfo("^([\\w\\W]*)(\\b\\s*)$", "$1"),
	nospaces : PrototypeValidator.filterInfo("\\s*", ""),
	nocommas : PrototypeValidator.filterInfo(",", ""),
	nodots : PrototypeValidator.filterInfo("\\.", ""),
	noquotes : PrototypeValidator.filterInfo("'", ""),
	nodoublequotes : PrototypeValidator.filterInfo('"', ""),
	nohtml : PrototypeValidator.filterInfo("<[^>]*>", ""),
	alphanumericonly : PrototypeValidator.filterInfo("[^\\w]", ""),
	numbersonly : PrototypeValidator.filterInfo("[^\\d]", ""),
	lettersonly : PrototypeValidator.filterInfo("[^a-zA-Z]", ""),
	commastodots : PrototypeValidator.filterInfo(",", "."),
	dotstocomma : PrototypeValidator.filterInfo("\\.", ","),
	numberscommas : PrototypeValidator.filterInfo("[^\\d,]", ""),
	numbersdots : PrototypeValidator.filterInfo("[^\\d\\.]", ""),
	nxx : PrototypeValidator.filterInfo("^[01]", "")
};

PrototypeValidator.load();

// FCKEditor / Prototype Event Compatibility layer
// Used internally by validator, but usable elsewhere too
function FCKeditor_OnComplete( editorInstance ) {
	function OnAfterLinkedFieldUpdate (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:FieldUpdate', editorInstance);
	}
	function OnAfterSetHTML (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:AfterSetHTML', editorInstance);
	}
	function OnFocus (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:Focus', editorInstance);
	}
	function OnBlur (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:Blur', editorInstance);
	}
	function OnPaste (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:Paste', editorInstance);
		return true; // Must return true else fck will block pastes
	}
	function OnStatusChange (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:StatusChange', editorInstance);
	}
	function OnSelectionChange (editorInstance, params) {
		$(editorInstance.LinkedField).fire('FCKeditor:SelectionChange', editorInstance);
	}
	editorInstance.Events.AttachEvent('OnAfterLinkedFieldUpdate', OnAfterLinkedFieldUpdate);
	editorInstance.Events.AttachEvent('OnAfterSetHTML', OnAfterSetHTML);
	editorInstance.Events.AttachEvent('OnFocus', OnFocus);
	editorInstance.Events.AttachEvent('OnBlur', OnBlur);
	editorInstance.Events.AttachEvent('OnPaste', OnPaste);
	editorInstance.Events.AttachEvent('OnStatusChange', OnStatusChange);
	editorInstance.Events.AttachEvent('OnSelectionChange', OnSelectionChange);
	$(editorInstance.LinkedField).fire('FCKeditor:OnComplete', editorInstance);
	$(editorInstance.LinkedField).FCKeditorInstance = editorInstance;
}