/* Form validating jQuery plugin
 * 
 * requires jquery, core.js
 * */
(function($){$.extend($.fn,{
	validate:function(settings){				
		if (!this.length){
			return;
			}
		var validator = $.data(this[0], 'validator');
		if ( validator ) {
			return validator;
			}
		validator = new FormValidator( this[0],settings );
		$.data(this[0], 'validator', validator);
		return validator;
		}
	});
})(jQuery);


var FormValidator=function(form,settings){
	this.settings=$.extend( {}, FormValidator.defaultSettings, settings );	
	this.form=$(form);
	this.initialize();
	}
	FormValidator.FIELD_ERROR_CLASS='fielderror';
	$.extend(FormValidator,{			
		defaultSettings:{
			fieldErrorClass:FormValidator.FIELD_ERROR_CLASS,
			rules:{},
			skipDisabled:true,
			skipInvisible:true
			},
		FIELD_VALIDATION_STATUS:{
			ok:1,
			error_mask:10,
			error_minlength:11,
			error_maxlength:12,
			error_length:13,
			error_compare:14,
			error_comparenot:15,
			error_minvalue:16,
			error_maxvalue:17,
			error_callback:18,
			error:100
			},
		defaultFieldRule:{},
		rules:{
			mail:{
				mask:/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i
				},
			date:{
//				mask:/^\d{2,4}[\.\/\-]\d{2}[\.\/\-]\d{2}(?:\s\d{2}[\.\/\-\:]\d{2}[\.\/\-\:]\d{2})$/,
				minlength:8,
				oninit:function(field){
					$(field).calend();
					},
				getVal:function(val, field){
					var dt = $(field).attr('date');
					if (dt){
						return $.idate(dt).set({
							h: 0,
							i: 0,
							s: 0
							}).getStr('_dt');
						}
					return val;
					}
				},
			freedate:{
//				mask:/^\d{2,4}[\.\/\-]\d{2}[\.\/\-]\d{2}(?:\s\d{2}[\.\/\-\:]\d{2}[\.\/\-\:]\d{2})$/,
				minlength:0,
				oninit:function(field){
					$(field).calend();
					}
				},
			url:{
				mask:/^((https?|ftp):\/\/)?(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
				},
			stricturl:{
				mask:/^((https?|ftp):\/\/)(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i
				},
			login:{
				mask:/^[A-Za-z0-9!@#\$%^&\*\(\)_\-\+\.]+?$/i,
				minlength:3
				},
			password:{
				mask:/^[a-zA-Z0-9\.\,\!\@\#\$\%\^\&\*\(\)_\-\+]+$/,
				minlength:6
				},
			password2:{
				mask:/^[a-zA-Z0-9\.\,\!\@\#\$\%\^\&\*\(\)_\-\+]+$/,
				minlength:8,
				compare:'password'
				},
			givenname:{
				mask:/^[a-zA-Zà-ÿÀ-ß¸¨\-]+$/i
				},
			captcha:{
				strictlength:6
				},
			phone:{
				minlength:1,
				mask:/^[0-9\+\-\s\(\)\.\#]+$/
				},
			hour:{
				mask:/[0-9][0-9]?/,
				minvalue:0,
				maxvalue:23
				},
			minute:{
				mask:/[0-9][0-9]?/,
				minvalue:0,
				maxvalue:59
				},
			notnullvalue:{
				minvalue:1
				},
			notemptyvalue:{
				callback:function(val){
					return !!trimStr(val);
					},
				getVal:function(val){
					return trimStr(val);
					}
				},
			trimmed:{
				getVal:function(val){
					return trimStr(val);
					}
				}
			},
		getErrorStr:function(error){
			for (var i in FormValidator.FIELD_VALIDATION_STATUS){
				if (FormValidator.FIELD_VALIDATION_STATUS[i]==error){
					return i;
					}
				}
			}
		});
	FormValidator.prototype={
		initialize:function(){			
			var $this=this;
			this.updateFields();
			this.putEvents();
			},
		putEvents:function(){
			var $this=this;
			this.form.submit(function(){
				return $this.submitFormHandler();
				});			
			//this.fields.focus(function(){$this.fieldFocusHandler(this)});
			//this.fields.blur(function(){$this.fieldBlurHandler(this)});			
			},
		updateFields:function(){
			var 
				$this=this,
				fSuffix = 
					(this.settings.skipDisabled ? ':enabled' : '') + 
					(this.settings.skipInvisible ? ':visible' : ''),
				$fields = $('input'+fSuffix+',input[type=hidden],textarea'+fSuffix+',select'+fSuffix,this.form);
			this.fields=$fields
				.each(function(num){
					if ($.data(this,'validator')){
						return;
						}
					var 
						$field = $(this),
						name = this.name,
						rules = duplicateObject($this.settings.defaultFieldRule);
					
					var sname=searchInClass(this,'field-');
					if (sname && FormValidator.rules[sname]){
						rules=$.extend({},rules,FormValidator.rules[sname])
						}
					
					if ($this.settings.rules[name]){
						rules=$.extend({},rules,$this.settings.rules[name])
						}
						
					if (rules && rules['oninit']){
						rules['oninit'].call(this,this);
						}	
						
					$.each(
						['focus','blur','change','keydown','keyup'],
						function(){
							var evnt=String(this);
							$field.bind(evnt,function(){
								var field=this;
								var rules=$.data(field,'rules');
								
								if (method_exists($this,'fieldHandler'+evnt.firstCharUpper())){
									$this['fieldHandler'+evnt.firstCharUpper()](field);
									}						
								if (rules && rules['on'+evnt]){
									rules['on'+evnt].call(field,field);								
									}
								if ($this.settings['onField'+evnt.firstCharUpper()]){
									$this.settings['onField'+evnt.firstCharUpper()].call(field,field);
									}
								
								})							
							}
						);
					var fc=function(){
						$this.fieldErrorFree(this);
						};
					$(this).click(fc).keydown(fc);
					$.data(this,'validator',$this);
					$.data(this,'rules',rules);
					});
			},
		validate:function(){
			var $this=this;
			var errors=0;
			var tofocus;
			this.errorFields={};
			$.each(this.fields,function(){
				var field=this;
				var fres=$this.validateField(field);				
				var rules=$.data(field,'rules');
				if (fres>FormValidator.FIELD_VALIDATION_STATUS.ok){
					if (!rules.warn){
						errors++;
						if (!tofocus){
							tofocus=field;
							}						
						if (field.name){
							$this.errorFields[field.name]=fres;
							}
						}
					$this.fieldError(field,fres);
					}
				else{
					if (field.name){
						$this.errorFields[field.name]=false;
						}
					}
				});
			$(tofocus).focus();
			return (errors==0);
			},
		validateField:function(field){
			var rules=$.data(field,'rules');
			var val=this.fieldVal(field);			
			if (rules){				
				if (rules.minlength && val.length<rules.minlength){
					return FormValidator.FIELD_VALIDATION_STATUS.error_minlength;
					}
				if (rules.maxlength && val.length>rules.maxlength){
					return FormValidator.FIELD_VALIDATION_STATUS.error_maxlength;
					}
				if ($.isFunction(rules.callback) && !rules.callback(val,field)){
					return FormValidator.FIELD_VALIDATION_STATUS.error_callback;
					}
				if (rules.minvalue != undefined && (isNaN(Number(val)) || Number(val)<rules.minvalue ) ){
					return FormValidator.FIELD_VALIDATION_STATUS.error_minvalue;
					}
				if (rules.maxvalue != undefined && (isNaN(Number(val)) || Number(val)>rules.maxvalue ) ){
					return FormValidator.FIELD_VALIDATION_STATUS.error_maxvalue;
					}
				if (rules.strictlength && val.length!=rules.strictlength){
					return FormValidator.FIELD_VALIDATION_STATUS.error_length;
					}
				if (rules.mask && !rules.mask.test(val)){
					return FormValidator.FIELD_VALIDATION_STATUS.error_mask;
					}				
				if (rules.compare && (!val || val!=this.fieldVal(this.fieldByName(rules.compare)))){
					return FormValidator.FIELD_VALIDATION_STATUS.error_compare;
					}
				if (rules.comparenot && (!val || val==this.fieldVal(this.fieldByName(rules.comparenot)))){
					return FormValidator.FIELD_VALIDATION_STATUS.error_comparenot;
					}
				}				

			return FormValidator.FIELD_VALIDATION_STATUS.ok;
			},
		fieldVal:function(field){
			return (field.type=='radio')?getRadioValue(field.name):$(field).val();
			},
		fieldByName: function( name ) {
			// select by name and filter by form for performance over form.find("[name=...]")
			var form = this.form[0];
			return $(document.getElementsByName(name)).map(function(index, element) {
				return element.form == form && element.name == name && element  || null;
				});
			},
		fieldError:function(field,error){
			var rules=$.data(field,'rules');
			if (rules.onerror){
				rules.onerror.call(field,field,FormValidator.getErrorStr(error));
				}
			if (this.settings.onFieldError){
				this.settings.onFieldError.call(field,field,FormValidator.getErrorStr(error));
				}
			if (!rules.warn){
				$(field).addClass(this.settings.fieldErrorClass);
				}
			return this;
			},
		fieldErrorFree:function(field){
			if ($(field).hasClass(this.settings.fieldErrorClass)){
				$(field).removeClass(this.settings.fieldErrorClass);
				if (this.settings.onFieldErrorFree){
					this.settings.onFieldErrorFree.call(field,field);
					}
				}
			},
		fieldHandlerBlur:function(field){
			var $this=this;
			var rules=$.data(field,'rules');
			if ((rules && rules.validateOnBlur) || this.settings.validateFieldsOnBlur){
				var fres=$this.validateField(field);
				if (fres>FormValidator.FIELD_VALIDATION_STATUS.ok){
					this.fieldError(field,fres);
					}
				}
			},
		submitFormHandler:function(){
			if (this.settings.onpresubmit){
				this.settings.onpresubmit.call(this.form[0]);
				}
			if (this.settings.updateFieldsOnSubmit){
				this.updateFields();
				}
			if (this.validate()){
				if ($.isFunction(this.settings.onsubmit)){					
					return this.settings.onsubmit(this.getResults(),this.form[0],this);
					}
				else{					
					return true;
					}
				}
			else{
				if ($.isFunction(this.settings.onerror)){
					this.settings.onerror(this.errorFields||{});
					}
				return false;
				}
			
			},				
		getResults:function(){
			var $this=this;
			var res={};
			$.each(this.fields,function(){
				var 
					field=this,
					key=this.name,
					val,
					rules=$.data(field,'rules');
				if (field.type=='radio'){
					val=res[key]==undefined?$this.fieldVal(this):res[key];
					}
				else if (field.type=='checkbox'){
					val=Number(field.checked);
					}
				else{
					key=field.name||field.id;
					val=$(field).val();
					}
				res[key]=(rules && rules.getVal)?rules.getVal(val,field):val;
				});
			return res;
			}
		};
