function formValidator(f,l){
	var formId=f||null,formObj,lang=l||'fr',depth=99100,uit=window.uiFormValidatorDrlk,ifrm=null;
	if(!isset(uit)) uit=window.uiFormValidatorDrlk={};
	var scrollToFirstError = true ;
	var get={
		value:function(o){return isset(o)?o.type=='radio'?o.checked?o.value:null:isset(o.value)?o.value:null:null;},
		item:function(o){
			var r=null;
			if(!isset(o)) return r;
			var o=formObj[o];
			if(!isset(o)) return;
			if(isset(o.nodeName)){r=o;}
			else{
				if(isset(o[0].nodeName)){
					for(var i=0;i<o.length;i++){if(isset(o[i].checked)&&o[i].checked) r=o[i];}
					if(!isset(r)) r=o[0];
				}
			}
			return r;
		},
		type:function(o){return o.type;}
	};
	var set={
		focus:function(o){
			if(isset(o.focus)) o.focus();
			if(isset(o.select)) o.select();
		}
	};
	function display(o,s){
		if(!isset(o)) return;
	
		var c1=jml.obj ;
		var c2=jml.xml.node ;
		var c3=jml.events;
		var o1=o.o;
		var o2=null;
		var x1=c2.create('DIV',document.body);
		var x2=c2.create('DIV',document.body);
		var ts='ui_'+o1.name;
		
		// patch #1
		if( lower( o.o.type ) == "hidden" ){
			var tempObject = getObj( o.n );
			if( isset( tempObject ) ){
				o2 = tempObject;
				var hidden_initial = o.v ;
				var hidden_intl = setInterval( function startLookup(){

					if( hidden_initial != o1.value ){
						clear();
						clearInterval( hidden_intl );
					}
				} , 50 );
			}
		}
		if(ifrm==null&&lower(navigator.userAgent).match('msie 6')!=null){
			ifrm=c2.create('DIV',document.body);
			c1.set(ifrm,'innerHTML','<iframe></iframe>');
			c1.set(ifrm,'className','ie6UnderFloatBugForm');
		}
		c1.set(x1,'innerHTML',s);
		c1.set(x2,'innerHTML','&nbsp;');
		c1.set(x1,'className','formError01');
		c1.set(x2,'className','errorArrow01');
		
		var __anchor = isset( o2 )? o2 : o1 ;
		
		var n1=c1.get(__anchor,'offsetLeft');
		var n2=c1.get(__anchor,'offsetTop');
		var n3=c1.get(__anchor,'offsetWidth');
		var n4=c1.get(__anchor,'offsetHeight');
		var n5=c1.get(x2,'offsetWidth');
		var n6=n3 < 120 ? 120 : n3;

		c1.set(x1,'width',n6+'px');
		c1.set(x1,'left',n1+'px');
		c1.set(x1,'top',(n2+n4)+'px');
		//c1.set(x1,'zIndex',depth-1);
		c1.set(x1,'zIndex','999999');
		
		var nn0 = 0 ; 
		if( typeof externalInIframe != "undefined" ){
			if( externalInIframe == true ){
				nn0 = 15 ;
			}
		}
		
		c1.set(x2,'left',(n1-(n5-(jml.browser.type=='msie'?8:6)) + nn0)+'px');
		c1.set(x2,'top',(n2-2)+'px');
		///c1.set(x2,'zIndex',depth);
		c1.set(x2,'zIndex','999999');
		o.err=x2;
		dCache.push(o);
		if( !isset( f ) && !isset(o2) ){
			set.focus(o1);
			f=o1;
		}
		c3.add(x2,'mouseover',function(){
			if(ifrm!=null){
				c1.set(ifrm,'width',(n6+10)+'px');
				c1.set(ifrm,'height',(c1.get(x1,'offsetHeight')+10)+'px');
				c1.set(ifrm,'left',n1+'px');
				c1.set(ifrm,'top',(n2+n4)+'px');
				c1.set(ifrm,'visibility','visible');
			}
			c1.set(x1,'visibility','visible');
			var b=false;
			if(isset(o1.id)&&typeof(jmlUISuggestor_virtual)!='undefined') b=(jmlUISuggestor_virtual[o1.id]);
			if(!b) set.focus(o1);
		});
		c3.add(x2,'mouseout',function(){
			c1.set(x1,'visibility','hidden');
			c1.set(ifrm,'visibility','hidden');
		});
		function clear(b){
			var b=b||false;
			if(!b) c3.remove(o1,'keydown',data);
			if(!b) c3.remove(o1,'change',data);
			c2.remove(x1);
			if(!b) c2.remove(x2);
			if(ifrm!=null)c1.set(ifrm,'visibility','hidden');
			if(!b) delete window.uiFormValidatorDrlk[ts];
		}
		function data(e){
			var e=c3.initialize(e);
			if(c3.getKey(e,true)!=-1)clear();
		}
		var to1=formObj[o1.name];
		if(isset(to1.length)&&!isset(to1.nodeName)){
			for(var i=0;i<to1.length;i++) c3.add(to1[i],'click',data);
		}else{
			c3.add(o1,'keydown',data);
			c3.add(o1,'change',data);
			if(lower(o1.type)=='checkbox') c3.add(o1,'click',data);
		}
		depth-=2;
		window.uiFormValidatorDrlk[ts]={clear:clear};		
		return false;
	}
	function parse(a1,a2,forced){
		var a3=mixArr(a1,a2),r=true,fI=null,forced=forced||false;
		function setPoint(o){
			var s1=window.location.href;
			var s2=s1.match(/^.[^#]*/);
			if(s2==null)s2=s1;
			if(s2!=null&&isset(s2[0])) s2=s2[0];
			if(fI==null){
				if(isset(o.id)&&!isEmpty(o.id)){
					window.location.href=s2+"#"+o.id;
					fI=o.id;
				}else{
					if(isset(formId)){
						window.location.href=s2+"#"+formId;
						fI=formId;
					}
				}
			}
		}
		for(var i=0;i<a3.length;i++){
			var e=new function(){
				var g=a3[i],o=get.item(g.input);
				return isset(o)?{o:o,v:get.value(o),n:o.name,t:g.validation,c:g.custom,msg:isset(g.msg)?g.msg:msg[g.validation][lang]}:{o:null,v:null,n:null,t:null,c:null};
			}

			if(!isset(validationType[e.t])){
				r=false;
				return r;
			}
			var l,b1=e.t=='empty';
			if(!b1&&isset(e.v)) l=validationType['empty'](e.v,e.o);
			if(isset(e.c)&&isFunc(e.c)){
				var cr = e.c();
				if(!cr.valid){
					r=display(e,cr.msg);
					if( scrollToFirstError ) setPoint(e.o);
				}
			}
			if(!l&&!b1&&e.t!='checked'&&e.t!='selected'){
					r=display(e,e.msg);
					if( scrollToFirstError ) setPoint(e.o);
			}else{
				l=validationType[e.t](e.v,e.o);
				if(!l){
					r=display(e,e.msg);
					if( scrollToFirstError ) setPoint(e.o);
				}
			}
		}		
		return r;
	}
	var validationType={
		empty:function(v,o){return !isEmpty(v);},
		alpha:function(v,o){return v.match(/\d+/)==null;},
		numeric:function(v,o){return v.match(/\D+/)==null;},
		alphanumeric:function(v,o){return v.match(/.+/)!=null;},
		noSpecialChars:function(v,o){return v.match(/[^\w.]+/)==null;},
		email:function(v,o){return lower(v).match(/^([\w-]+)(\.[\w-]+)*@([a-z\d-]+)(\.[a-z\d-]+)*(\.[a-z]{2,4})$/)!=null;},
		multipleEmail:function(v,o){return lower(v).match(/^(([\w-]+)(\.[\w-]+)*@([a-z\d-]+)(\.[a-z\d-]+)*(\.[a-z]{2,4})(\s?)(,?)(\s?))+$/)!=null;},
		phone:function(v,o){return v.match(/[a-zA-Z]+/)==null&&v.replace(/\D*/g,'').length<=10&&v.match(/^[0-9\\-\\(\\)\\ \\.-]{10,14}$/)!=null;},
		postal:function(v,o){return upper(v).match(/^([ABCEGHJKLMNPRSTVXY]\d[ABCEGHJKLMNPRSTVWXYZ])\ {0,1}(\d[ABCEGHJKLMNPRSTVWXYZ]\d)$/)!=null;},
		checked:function(v,o){return o.checked;},
		selected:function(v,o){return isset(v)&&!isEmpty(v);},
		price:function(v,o){return v.match(/^[\d,\.\s]+$/)!=null&&v.match(/[\d]+/)!=null;},
		url:function(v,o){return v.match(/((file|ftp|https?):\/\/)?[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#]){1,}/)!=null;}
	};
	var msg={
		empty:{
			fr:"Ce champ est vide!",
			en:"This field is empty!",
			es: "Este campo est&aacute; vaci&iacute;o."
		},
		alpha:{
			fr:"Les caractères numériques ne sont pas permis!",
			en:"The numeric characters are not allowed!",
			es:"Los caracteres num&eacute;ricos no son permitidos."
		},
		numeric:{
			fr:"Ce champ devrait contenir uniquement des caractères numériques!",
			en:"This field should only contain numeric characters!",
			es:"&iexcl;Este campo deber&iacute;a contener solamente caracteres num&eacute;ricos!"
		},
		alphanumeric:{
			fr:"Ce champ devrait uniquement contenir des caractères alpha-numériques!",
			en:"This field should only contain alpha-numeric characters!",
			es:"&iexcl;Este campo deber&iacute;a contener solamente caracteres num&eacute;ricos!"
		},
		noSpecialChars:{
			fr:"Ce champ devrait uniquement contenir des caractères alpha-numériques et ne pas contenir de caractères spéciaux tel que é ou / ou &, etc.",
			en:"This field should only contain alpha-numerical characters. (No special characters such as é and / or &, and so on.)",
			es:"&iexcl;Este campo deber&iacute;a contener solamente caracteres num&eacute;ricos! (Los caracteres especiales como Ã©, /, &, etc no son permitidos.)"
		},
		email:{
			fr:"Cette adresse de courriel n'est pas valide!",
			en:"This email address is not valid!",
			es:"&iexcl;Esta direcci&oacute;n de correo electr&oacute;nico no es v&aacute;lida!"
		},
		multipleEmail:{
			fr:"Une de ces adresses de courriel n'est pas valide!",
			en:"One of these email addresses is not valid!",
			es:"Uno de los correos electrónicos no es válido."
		},
		phone:{
			fr:"Ce numéro de téléphone n'est pas valide!",
			en:"The phone number is not valid!",
			es:"&iexcl;Este n&uacute;mero de tel&eacute;fono no es v&aacute;lido!"
		},
		postal:{
			fr:"Ce code postal n'est pas valide!",
			en:"The zip code is not valid!",
			es:"&iexcl;Este c&oacute;digo postal no es v&aacute;lido!"
		},
		validation:{
			fr:"Ce type de validation n'est pas disponible!",
			en:"This type of validation is not available!",
			es:"Este tipo de validaci&oacute;n no est&aacute; disponible."
		},
		checked:{
			fr:"Vous devez cocher ce champ afin de poursuivre.",
			en:"You must check this field to continue.",
			es:"Para continuar debe seleccionar este campo."
		},
		selected:{
			fr:"Il n'y a aucune option de sélectionner!",
			en:"There is no selected option!",
			es:"&iexcl;No ha seleccionado ninguna opci&oacute;!"
		},
		price:{
			fr:"Le prix ne peut contenir que des chiffres, des virgules, des points ou des espaces. Il ne peut y avoir d'autres caractères",
			en:"The price can contain numbers, commas, periods or spaces. (There can be no other type of characters.)",
			en:"The price can contain numbers, commas, periods or spaces. (There can be no other type of characters.)"
		},
		url:{
			fr:'Cette adresse n\'est pas valide!',
			en:'This address (url) is not valid!',
			es: "Esta direcci&oacute;n web (url) no es v&aacute;lida!"
		}
	};	
	return {
		ctrl:function(){
			var h=this;
			jml.events.add(document.body,'__textResize',delegate(h,'errorPosition'));
			return this;
		},
		errorPosition:function(){
			var c1=jml.obj,c2=jml.xml.node;
			if(typeof(dCache)=='undefined') return;
			if(!isset(dCache)) return;
			var a=dCache;			
			for(var i=0;i<a.length;i++){
				var e=a[i];
				var n1=c1.get(e.o,'offsetLeft'),n2=c1.get(e.o,'offsetTop'),n3=c1.get(e.o,'offsetWidth'),n4=c1.get(e.o,'offsetHeight'),n5=c1.get(e.err,'offsetWidth'),n6=n3<120?120:n3;
				c1.set(e.err,'left',(n1-(n5-(jml.browser.type=='msie'?8:6)))+'px');
				c1.set(e.err,'top',(n2-2)+'px');
			}
		},
		fields:{statics:[],dynamics:[]},
		validate:function( gotoFirstError ){
			if( typeof gotoFirstError != 'undefined' ) scrollToFirstError = gotoFirstError ;
			dCache=[];
			formObj=getObj(formId);
			return isset(formObj)?parse(this.fields.statics,this.fields.dynamics):false;
		},
		validateField:function(o){
			dCache=[];
			formObj=getObj(formId);
			return isset(formObj)?parse([o],[],true):false;
		},		
		clearError:function(){
				var k=uiFormValidatorDrlk;
				for(var j in k) if(isset(k[j].clear)) k[j].clear();		
		}
	}.ctrl();
}