/*	FormValidator
 *  (c) 2007 Costache Catalin, catacgc@gmail.com
 *
 *	All rights reserved.
 *	You can use / modify this script in your Web pages, provided these opening credit
 *  lines are kept intact.

 	Examples:

 	HTML CODE:
 	<form ...>
	 	<input type='text' 		name='a' id='a' value='24.5'/>
	 	<input type='text' 		name='b' id='b' value='mail@mail.co'/>
	 	<input type='checkbox'	name='c' id='c' value='3'/>
 	</form>

 	var validator = new FormValidator({
 		onFailure: function(id, messge){
 			alert(id + ': ' + message);
 		}
 	});

 	validator.add({
		name:		['a','b','c'],
		type:		'required',
		message:	'the field is required'
 	})

 	validator.add({
 		id:	  'a',
 		type: ['number','size|max=24.4'],
 		message:	'you must think of a number lower than 24.4'
 	})

 	validator.add({
 		id:		'b',
 		type:	'email',
 		event:	'keyup',
 		onFailure:	function(id){
			$(id).style.border = '1px solid #FF0000';
 		},
 		onSuccess:	function(id){
			$(id).style.border = '1px solid #00FF00';
 		}

 	})

 	function submit(){
 		if(validator.validateAll()){
 			form.submit();
 		}
 	}


 	Documentation:

 	methods:

 	initialize( Object arguments );	-	constructor for the validator
		form		[string optional]	-	id of the form to validate
		onSuccess	[handle(id) optional]	-	default callback for successfully validated element
		onFailure	[handle(id, message) optional]	-	default callback for unsuccessfully validated element
		onValidateAll[handle(ids) optional]	- called when validateAllMethod is called; ids - all the validated elements ids
		event		[string optional]	-	default event to observe

 	add( Object arguments );	- main method to add validaton to form items
 		id:			[string/array mandatory]	-	id of the element(s) to observe (trasmited to the onSuccess and onFailure callbacks)
 		type:		[string/array mandatory]	-	validator type/name ( go to 'Default validators' )
 		onSuccess:	[handle(id) optional]	-	if set, it overrides the callback from the constructor
		onFailure:	[handle(id, message) optional]	-	if set, it overrides the callback from the constructor
		parameters: [hash	optionsl]	-	parameters for the selected validator ( go to 'Default validators' )
		event:		[string optional]	-	event to observe ( besides the one from the constructor )
		message:	[string optional]	-	message transmited to the onFailure callback when a validation fails

	register (string name, handle validationHandle)		-	register a new validator
		name:				-	name of the new validator (Eg: 'mail') (overrides the validator whith the same name)
		validationHandle:	-	functinon of form handle(id, Object parameters) which returns false if the validation criteria fails on the $(id) element, otherwise true

	remove (string id)	-	removes validations from an element
		id:		-	the id of the element on wich to clear all validations

	validateAll()	-	master function which triggers all validations on all elements ( this can be called
						manually but it is also called automatically if the form parameter is set in the
						constructor and the onsubmit event is triggered by a button or an input
		@return boolean


/*--------------------------------------------------------------------------*/
var FormValidator = Class.create();

FormValidator.prototype = {
	initialize: function(arguments) {
		//constructor
		if(typeof(arguments) != 'object')
			arguments = {};
			
		this.form		 		= arguments.form 		|| null;
		this.successCallback 	= arguments.onSuccess 		|| Prototype.emptyFunction;
		this.failureCallback 	= arguments.onFailure 		|| Prototype.emptyFunction;
		this.onValidateAllCallback = arguments.onValidateAll	|| Prototype.emptyFunction;
		this.event				= arguments.event 	||	'none';						//or any other event: blur, change, mouseout, mouseover, etc
		this.ajax				= arguments.ajax	||	null;						//request by ajax
		
		if(this.form){
			Event.observe(this.form, 'submit', function(event) { 
					var validated = this.validateAll();
					if( !validated ) event.preventDefault();
					if( validated && this.ajax ) {
						event.preventDefault();
						if(Object.isFunction(this.ajax.handler)) {
							var handler = this.ajax.handler.bind(this);
							handler();
						}
						else {
							$(this.form).request(this.ajax);
						}
					}
				}.bind(this)
			);
		}

		this.validatorTypes = $H({
			email	: 	this.emailValidator,
			length	: 	this.lengthValidator,		//['length|min=6|max=20'] - for strings length
			numeric	: 	this.numericValidator,
			number	: 	this.numberValidator,
			alpha	:	this.alphaValidator,
			alnum	:	this.alphaNumValidator,
			username:	this.usernameValidator,
			size	:	this.sizeValidator,			//	['size|min=10.23|max=123.23]
			required: 	this.requiredValidator,		//	['required']	-	make sure that the field is not empty
			equals	: 	this.equalsValidator,		//	['equals|name=confirmPassword] - two fields are equal ( mainly for passwords )
			regexp	:	this.regexpValidator,		//	["regexp|pattern=[0-9a-Z]|modifier=g"] - the value must match the pattern; [!!] If you want to match the entire string, do not forget to provide string delimiters: ^ and $
			least	:	this.leastValidator
		})


		this.validators = $H({});
	},
	add: function (arguments){
		if(typeof($(arguments.id)) == 'undefined' && typeof($(arguments.name)) == 'undefined'){
			throw 'id or name of the validator is mandatory';
			return;
		}
		if(typeof(arguments.type) == 'undefined'){
			throw 'type of the validator is mandatory';
		}
				
		var ids = new Array();
		if( arguments.id ){
			ids = ids.concat(arguments.id);
		}
		if( arguments.name ){
			ids = ids.concat(arguments.name);
		}
		
		var msgs = new Array(); 
		msgs = msgs.concat(arguments.message);

		/**
		 * Exclusive validation AND
		 */
		var types = new Array();
		types = types.concat(arguments.type);
		
		var parameters = arguments.parameters   || {};

		for(var jj = 0; jj < ids.length  ; jj++){
			var id = ids[jj];
			var validator = null;
			for(var ii = 0; ii < types.length; ii++){
				var type = types[ii];
				//split a string like 'length|max=20|min=10' into type: length and parameters = {max: 20, min: 10}
				var sep = type.indexOf('|');
				if(sep != -1){
					var paramBlock = type.split('|');
					type = paramBlock[0];
					for(var i = 1; i < paramBlock.length; i++ ){
						var eq = paramBlock[i].indexOf('=');
						var obj = new Object();
						obj[paramBlock[i].substr(0,eq)] = paramBlock[i].substr(eq+1);
						parameters = Object.extend(parameters, obj);
					}
				}
				
				var m = msgs[ii] == 'undefined' ? " " : msgs[ii];		//default message
				//multiple validators on same element
				if(typeof(this.validators.get(id)) == 'undefined'){
					validator = $H({
						id				:id,
						types				:[type],
						successCallback 	:arguments.onSuccess 	|| this.successCallback,
						failureCallback 	:arguments.onFailure 	|| this.failureCallback,
						parameters			:parameters   			|| {},
						event				:arguments.event 		|| this.event,
						message				:[m]
					})
				}else{
					validator 	= this.validators.get(id);
					validator.set('event',arguments.event || validator.get('event'));
					validator.get('types').push(type);
					validator.get('message').push(m);
					validator.set('parameters' , Object.extend(validator.get('parameters'),parameters));
				}
				this.validators.set(id,validator);
			};

			if(validator.get('event') != 'none'){

				Event.observe(
					id,
					validator.get('event') ,
					function(){this.validate(id)}
				);
			}
		}
	},
	remove:	function (key) {
		this.validators.unset(key);
	},
	validate: function ( id ){
		var validator = this.validators.get(id);
		for(ii = 0; ii < validator.get('types').length; ii++){
			var handler = this.validatorTypes.get( validator.get('types')[ii] );
			try {
				var result = handler( id, validator.get('parameters') );
			} catch( ex ){
				alert('Validation exception in ' + handler + '(' + id + ',' + validator.get('parameters') + ')');
			}
			if( result ){
				try {
					validator.get('successCallback')(id, ii);
				} catch(ex){
					alert('Success callback error:' + ex.message);
				}
			} else {
				var message = validator.get('message')[ii];
				try {
					validator.get('failureCallback')(id, message, ii);
				} catch(ex){
					alert('Failure callback error:' + ex.message);
				}
				return false;
			}	
		}
		return true;
	},
	validateAll: function (){
		var mkeys = this.validators.keys();
		this.onValidateAllCallback(mkeys);
		var result = true;
		for( var ii = 0 ; ii < mkeys.length; ii++){
			good = this.validate(mkeys[ii]);
			if(!good){
				result = false;						//dont't break beacause we want all errors
			}
		}
		return result;
	},
	forceSuccess: function( ids ){
		ids.each(
			function( id ){
				this.validators.get(id).get('successCallback')(id);
			}
		);
	},
	forceFailure: function( ids ){
		ids.each(
			function( id ){
				this.validators.get(id).get('failureCallback')(id);
			}
		);
	},
	register: function(name, validationMethod){
		this.validatorTypes.set(name,validationMethod);

	},
	/**
	 * Gets an element by name and if not found fallback to id
	 */
	$		: function(name){
		var elements = $A(document.getElementsByName(name));
		if( count(elements) ){
			return elements[0];
		}else{
			return $(name);
		}
	},
	/*********************** IMPLICIT VALIDATORS ******************************/
	emailValidator: function(id,parameters){
		emailStr  = $(id).value;
		if(!emailStr.length){		//don't verify empty strings
			return true;
		}
		var splitted = emailStr.match("^(.+)@(.+)$");
		if(splitted == null){
			return false;
		}
		if(splitted[1] != null )
		{
			var regexp_user=/^\"?[\w-_\.]*\"?$/;
			if(splitted[1].match(regexp_user) == null)
			{
				return false;
			}
		}
		if(splitted[2] != null)
		{
			var regexp_domain=/^[\w-\.]*\.[A-Za-z]{2,4}$/;
			if(splitted[2].match(regexp_domain) == null)
			{
				var regexp_ip =/^\[\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\]$/;
				if(splitted[2].match(regexp_ip) == null){
					return false;
				}
			}
			return true;
		}
		return false;
	},
	textareanouValidator: function(id, parameters){
		var element = $(id);
		var type = element.type;
		if(type != 'textarea'){
			return false;
		}
		if(	parameters['max'] && $(id).innerHTML.length > parameters['max'] ){
			return false;
		}
		if(	parameters['min'] && $(id).innerHTML.length < parameters['min'] ){
			return false;
		}
		return true;
	},
	lengthValidator: function(id, parameters){
		if(	parameters['max'] && $(id).value.length > parameters['max'] ){
			return false;
		}
		if(	parameters['min'] && $(id).value.length < parameters['min'] ){
			return false;
		}
		return true;
	},

	numericValidator: function(id, parameters){
		var num_regex = /[^0-9]/;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},
	numberValidator: function(){
		var value = $(id).value;
		if(value.length > 0 && isNaN(value)){
			return false;
		}
		return true;
	},
	alphaValidator: function(id, parameters){
		var num_regex = /[^a-z]/i;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},

	alphaNumValidator: function(id, parameters){
		var num_regex = /[a-z]{1}[^a-z0-9]/i;
		var charpos = $(id).value.search(num_regex);
		if($(id).value.length > 0 && charpos >= 0){
			return false;
		}
		return true;
	},
	//Validates a username that starts with a letter and is made of a-z0-9_ chars
	usernameValidator: function(id, parameters){
		var pattern = /^[a-z]+[a-z0-9_]*$/i;

		if(!$(id).value.match(pattern)){
			return false;
		}
		return true;
	},
	sizeValidator:	function(id, parameters){
		var val = parseFloat($(id).value);

		if(val.length == 0 || isNaN(val)){
			return false;
		}
		if(typeof(parameters.max) != 'undefined' && val > parameters.max){
			return false;
		}
		if(typeof(parameters.min) != 'undefined' && val < parameters.min){
			return false;
		}
		return true;
	},
	requiredValidator: function(id, parameters){
		var element = $(id);
		var type = element.type;
		if(type == 'checkbox' && !element.checked){
			return false;
		}
		if(element.value.length == 0){
			return false;
		}
		return true;
	},
	equalsValidator: function(id, parameters){
		var element = $(id);
		if( element.value != $(parameters.id).value ){
			return false;
		}

		return true;
	},
	leastValidator: function(id, parameters){
		var elements = $A(document.getElementsByName(parameters.name));
		good = false;
		elements.each(
			function( el ){
				el = $(el);
				if( el.checked == true){
					good = true;
				}
			}
		);
		return good;

	},
	regexpValidator: function(id, parameters){
		if($(id).value.length > 0)
		{
			var reg;
			if(typeof(parameters['modifier']) != 'undefined') {
				reg = new RegExp(parameters['pattern'], parameters['modifier']);
			}
			else{
				reg = new RegExp(parameters['pattern']);
			}
			
			if(!reg.match($(id).value)) {
				return false;
			}
		}
		return true;
	}
}

