/**************************************************************************************************************************

					Funciones para el manejo de FORMULARIOS (C) Alberto Blanco 2008

					 	Funciones y clases para el manejo de campos y formularios
		
		
**************************************************************************************************************************/


/*************************************************************************************************
 En FRMBtns se llevan las instancias de los objetos FRMBoton, creadas a partir de FRMBotonById
**************************************************************************************************/
var FRMBtns=new Array();

/***************************************************************************************
 Crea una nueva instancia del la clase FRMCampo para un campo buscándolo por el nombre
****************************************************************************************/ 
function FRMBotonById(Id,param)
{
var obj=document.getElementById(Id);
var i;

	if (typeof(obj) != 'undefined')
		{
		i=FRMBtns.length;
		FRMBtns[i]=new FRMBoton(obj,param);		//Creo la referencia numérica
		FRMBtns[obj.id]=FRMBtns[i];			//Y la referencia por nombre del campo
		}
	else
		alert('No encontre el botón con id '+ Id + ' para crear FRMBoton');
};


/***************************************************************************************************
 En FRMCmpsREL se llevan las instancias de los objeto FRMCampoREL, creadas a partir de FRMRelacion
 Se referencian directamente contra el nombre pasado y por indice numerico
 Ej. FRMCmpsREL['migrupo'] o FRMCmpsREL.migrupo o FRMCmpsREL[15]
***************************************************************************************************/
var FRMCmpsREL=new Array();

/****************************************************
 Crea una nueva instancia del la clase FRMCampoREL
 Devuelve el número del indice creado
*****************************************************/ 
function FRMRelacion(Nombre,SQL,ListaCodigos,ListaRelacionados,NumBusqueda)
{
var i;

	i=FRMCmpsREL.length;
	FRMCmpsREL[i]=new FRMCampoREL(Nombre,SQL,ListaCodigos,ListaRelacionados,NumBusqueda);	//Creo referencia numérica
	FRMCmpsREL[Nombre]=FRMCmpsREL[i]; //Creo referencia por nombre
	return i; //Devuelvo el índice
};


/******************************************************************************************************************
 En FRMCmps se llevan las instancias de los objetos FRMCampo, creadas a partir de FRMCampoByName
 Se referencian directamente contra la propiedad name del objeto y por indice numerico
 Ej. FRMCmps[document.forms[0].elements[0].name] o FRMCmps['micampo'] o FRMCmps.micampo o FRMCmps[0] o FRMCmps[12]
*******************************************************************************************************************/
var FRMCmps=new Array();

/***************************************************************************************
 Crea una nueva instancia de la clase FRMCampo para un campo buscándolo por el nombre
****************************************************************************************/ 
function FRMCampoByName(Nombre,param)
{
var objs=document.getElementsByName(Nombre);
var cmp=null;
var i;

	if (objs.length>0) cmp=objs[0];
	if (cmp!=null)
		{
		i=FRMCmps.length;
		FRMCmps[i]=new FRMCampo(cmp,param);		//Creo la referencia numérica
		FRMCmps[cmp.name]=FRMCmps[i];			//Y la referencia por nombre del campo
		}
	else
		alert('No encontre el nombre de campo '+ Nombre + ' para crear FRMCampo');
};





/*****************************************************************************************




				 Clase para la gestion de los botones
				 
				 
				 
				 
******************************************************************************************/
function FRMBoton(btn,param)
{
var clase=this;	//Referencia a nosotros mismos, para las funciones internas

	//Exportacion de métodos
	this.click=mtClick;		//simula el click del boton
	this.enabled=mtEnabled; //Pone enabled / disabled el elemento asociado a la clase, según flag

	//Propiedades y sus valores por defecto.
	this.obj=btn;
	this.group=null;
	this.value='button';
	this.tabIndex=1;
	this.clickFunction=null;
	this.keyAccess=null;
	this.keyAv=null;
	this.keyRe=null;
	this.iclass=null;
	this.iclassFocus=null;
	this.iclassOver=null;
	this.iclassDisabled=null;
	this.lastFocus=false;	//Le indica si ha sido el último boton en tener el foco
	this.blocked=false;		//Flag de bloqueo

	//Comprobamos que el objeto sea un boton
	if (btn.tagName!='INPUT' || btn.type!='button')
		{
		MiError('El elemento asigando no es un botón.');
		return;
		};

	RecogeParametros(param); 	//Analizamos y recogemos los parámetros

	//Datos exportados
	this.obj.tabIndex=this.tabIndex;	//El tabindex
	this.obj.value=this.value;			//El value

	//Control de las clases
	if (this.iclass!=null) this.obj.className=this.iclass;
	if (this.iclassFocus!=null)
		{
		EVTAsignaEvento(this.obj, 'focus', evClass);
		EVTAsignaEvento(this.obj, 'blur', evClass);
		};
	if (this.iclassOver!=null)
		{
		EVTAsignaEvento(this.obj, 'mouseover', evClass);
		EVTAsignaEvento(this.obj, 'mouseout', evClass);
		};

	//Si tiene funcion de click, capturamos el click
	if (this.clickFunction!=null) EVTAsignaEvento(this.obj, 'click', evClick);

	//Si tiene teclas de acceso, se las ponemos al title
	if (this.keyAccess!=null) this.obj.title=FRMTextos[5] + ' ' + this.keyAccess;

	//Control de foco para actualizar el último boton que tuvo el foco
	EVTAsignaEvento(this.obj, 'focus', evBotonFoco);
	
	//Teclas de avance / retroceso
	//El campo lo movemos con el keydown. (en firefox, aun parando el evento, si el elemento es un select,
	//recibe la pulsacion de la tecla)	
	if (this.keyAv!=null)
		{
		EVTAsignaEvento(this.obj, 'keydown', evKeyAv);
		EVTAsignaEvento(this.obj, 'keyup', evKeyAv);
		EVTAsignaEvento(this.obj, 'keypress', evKeyAv);
		};
		
	if (this.keyRe!=null) 
		{
		EVTAsignaEvento(this.obj, 'keydown', evKeyRe);
		EVTAsignaEvento(this.obj, 'keyup', evKeyRe);
		EVTAsignaEvento(this.obj, 'keypress', evKeyRe);
		};



	/*****************************************************************
	
				 FUNCIONES INTERNAS DE LA CLASE


	*****************************************************************/
	
	/*********************************************************************************
	Funcion para recoger los parámetros
	*********************************************************************************/
	function RecogeParametros(prop)
	{
		if (prop.length==0) return;

	//Array referenciado por los nombres de las propiedades que se le pueden pasar en la cadena de parametros
	//0-cadena, 1-numero, 2-bool, 3-cadena opcional 10-opcion, 11-lista teclas
	var propiedades=new Array();
		propiedades['group']=[3];
		propiedades['value']=[0];
		propiedades['tabIndex']=[1];
		propiedades['clickFunction']=[0];
		propiedades['keyAccess']=[11];
		propiedades['keyAv']=[11];
		propiedades['keyRe']=[11];
		propiedades['iclass']=[0];
		propiedades['iclassFocus']=[0];
		propiedades['iclassOver']=[0];
		propiedades['iclassDisabled']=[0];


	var p,j,i,lis,flag;
	var miprop,propNombre,propValor;

		p=prop.split(';');			//Me creo un array con los grupos de propiedad:valor
		for (i=0;i<p.length;i++)	//Me lo recorro
			{
			//Separamos el nombre de la propiedad de su valor
			j=p[i].indexOf(':');	//Buscamos dentro del grupo el primer caracter ':'	
			if (j==-1)
				{
				//Esa propiedad no tiene valor
				propNombre=trim(p[i]);
				propValor='';
				}
			else
				{
				//La propiedad hasta los ':' y a partir de ahí, el valor de la misma
				propNombre=trim(p[i].substr(0,j));
				propValor=p[i].substr(j+1);
				};
					
			//Buscamos la propiedad en la matriz
			if (typeof propiedades[propNombre]!='undefined')
				{
					//Le asigno a miprop la referencia a la propiedad de la clase, que me monto a partir del parametro
					miprop='clase.' + propNombre;
					//Dependiendo del tipo de propiedad
					switch (propiedades[propNombre][0])
						{
						//Asignacion de una cadena
						case 0:
							if (propValor=='')
								MiError('La propiedad ' + propNombre + ' debe contener una cadena.');
							else
								{
								//Sustituimos las  " por \" para que no le afecte al eval									
								propValor=propValor.replace(/"/g,'\\\"');	
								eval( miprop + ' = "' + propValor + '";' );
								};
							break;
							
						//Asignacion de un numero entero
						case 1:
							if (isNaN(propValor))
								MiError('La propiedad ' + propNombre + ' debe ser numerica.');
							else
								eval( miprop + '=parseInt("' + propValor + '",10);' );
							break;
						
						//Asignacion de un booleano
						case 2:
							propValor=trim(propValor.toLowerCase());	//el valor a minusculas y le quitamos los espacios
							if (propValor!='true' && propValor!='false')
								MiError('La propiedad ' + propNombre + ' debe ser true o false.');
							else
								eval( miprop + '=' + propValor + ';' );
							break;
							
						//Asignacion de cadena opcional
						case 3:
							propValor=propValor.replace(/"/g,'\\\"');	//Sustituimos las  " por \" para que no le afecte al eval
							eval( miprop + ' = "' + propValor + '";' );
							break;
							
						//Asignacion de una cadena con un valor preestablecido
						case 10:
							lis=propiedades[propNombre][1].split(',');	//Nos monatamos una matriz con las opciones válidas
							propValor=trim(propValor);	//Le quitamos los espacios al valor por si acaso
							flag=false;
							for (j=0;j<lis.length;j++)	//Buscamos en la lista a ver si la propiedad existe
								if (lis[j]==propValor)
									{
									flag=true;
									j=lis.length; //Nos salimos del bucle
									};
							if (flag)
								eval( miprop + ' = "' + propValor + '";' );	//Asignamos
							else
								MiError('La propiedad ' + propNombre + ' debe ser uno de estos valores:\n' + propiedades[propNombre][1]);
							break;

						//Secuencia de teclas
						case 11:
							eval( miprop + ' = "' + propValor + '";' );
							break;	
						};
				}
			else
				MiError('Propiedad ' + propNombre + ' no valida.');
			};
	};

	// Muestra un error de la clase
	 function MiError(txt)
	 {
		 alert(txt + '\nBoton:' +	clase.obj.id);
	 };


	/*****************************************************************
	
					 EVENTOS INTERNOS DE LA CLASE

	*****************************************************************/

	//Control de las clases cuando coge o suelta el foco
	function evClass(evt)
	{
		evt=EVTDimeEvento(evt);	//Cogemos la referencia valida al evento dependiendo del navegador
		
		//Pierde el foco o el ratón se sale de encima
		if (evt.type=='blur' || evt.type=='mouseout')
			if (clase.iclass!=null)
				clase.obj.className=clase.iclass;
			else
				clase.obj.className='';
		
		//Coge el foco
		if (evt.type=='focus' && clase.iclassFocus!=null)
				clase.obj.className=clase.iclassFocus;
				
		//Raton encima
		if (evt.type=='mouseover' && clase.iclassOver!=null)
				clase.obj.className=clase.iclassOver;
	};

	//Control de las teclas de avance. La dispara keydown
	function evKeyAv(evt)
	{
		if (FRMVerificaClick(clase.keyAv,evt))
			{
			EVTNoPropagarEvento(evt);
			if (evt.type=='keydown') FRMFocoSig(clase.obj);
			};
	};

	//Control de las teclas de retroceso. La dispara keydown
	function evKeyRe(evt)
	{
		if (FRMVerificaClick(clase.keyRe,evt))
			{
			EVTNoPropagarEvento(evt);
			if (evt.type=='keydown') FRMFocoAnt(clase.obj);
			};
	};

	//Control de que este boton es el último que ha recibido el foco
	function evBotonFoco(evt)
	{
	var i;
	
		//Ponemos todos a false
		for (i=0;i<FRMBtns.length;i++)
			FRMBtns[i].lastFocus=false;
		//Y a este a true
		clase.lastFocus=true;
	};

	//Lanzamos la funcion asociada al click, si el boton no esta bloqueado
	function evClick(evt)
	{
		if (!clase.blocked)	eval(clase.clickFunction);
	};

	/*****************************************************************
	
					 METODOS DE LA CLASE

	*****************************************************************/
	//Pone enabled / disabled el elemento asociado a la clase, según flag
	function mtEnabled(flag)
	{
		if (flag)
			{
			//Enabled
			clase.obj.disabled=false;
			if (clase.iclass!=null)
				clase.obj.className=clase.iclass;
			else
				clase.obj.className='';
			}
		else
			{
			//Disabled
			clase.obj.disabled=true;
			if (clase.iclassDisabled!=null) clase.obj.className=clase.iclassDisabled;
			};
	};
	
	//Simula ckick
	function mtClick()
	{
		clase.obj.focus();
		clase.obj.click();
	};
};



/*****************************************************************************************




				 Clase para la gestion de los campos relacionados con BBDD
				 
				 
				 
				 
******************************************************************************************/
function FRMCampoREL(Nombre,SQL,ListaCodigos,ListaRelacionados,NumBusqueda)
{
//Variables globales
var clase=this;		//Referencia a nosotros mismos, para las funciones internas
var cmpIFRAME;
var cmpFORM;
var cmpCLV;			//Claves. Instancias clase FRMUnCampoREL (nombres campos BBDD y elemento asociado)
var cmpREL=null;	//Relacionados Instancias clase FRMUnCampoREL (nombres campos BBDD y elemento asociado)
var cmpFlagLectura=null;	//Flag de lectura
var cmpPag=SQL + '?Tipo=B&Action=' + NumBusqueda;	//página a cargar

	//Exportacion de propiedades
	this.name=Nombre;
	this.reading=false;		//true cuando comienza el proceso de lectura y false cuando termina
	this.win=null;
	
	//Exportacion de métodos
	this.readData=mtGetData;			//Lee los datos y los actualiza
	this.statusReset=mtResetStatus;		//Se le pasa true o false y reseta el flag de lectura y valores ini a true o null
	this.statusData=mtDataStatus;		//Devuelve el estado de la lectura de los datos
	this.winSearch=mtWinSearch;			//Abre la ventana de busqueda para los datos relacionados
	this.winProcess=mtWinProcess;		//Abre la ventana del mantenimiento para los datos relacionados
	
	//Inicializamos campos
	cmpCLV=CogeCmp(ListaCodigos);									//Cogemos los campos clave
	if (ListaRelacionados) cmpREL=CogeCmp(ListaRelacionados);		//Cogemos los campos relacionados
	
	//
	//Nos creamos el IFRAME
	//
	cmpIFRAME=document.createElement('IFRAME');
	cmpIFRAME.style.position='absolute';
	cmpIFRAME.style.visibility='hidden';
	cmpIFRAME.name='IFRAME' + Nombre;
	document.body.appendChild(cmpIFRAME);
		//Para ver el IFRAME y saber que pasa
		/*
		cmpIFRAME.style.top='0px';
		cmpIFRAME.style.left='700px';
		cmpIFRAME.style.height='400px';
		cmpIFRAME.style.visibility='visible';
		*/
	
	
	/***************************************************************
				 Funciones internas de la clase
	***************************************************************/
	
	//Si no tenía definida la ventana, la define
	function DefineWin()
	{
		if (clase.win==null) clase.win=new WINClassVentana('tipo:IFRAME,centrar:all,ajustar:all,noselect:true');
	};
	
	//Coge una lista de campos, la valida y la devuelve como matriz
	function CogeCmp(lista)
	{
	var dat=new Array();
	var tmp=lista.split(',');
	var j,i,ref,campoForm,campoBBDD,t;
	
		for (i=0;i<tmp.length;i++)
			{
			t=tmp[i].split(':');	//Separamos el nombre del campo del formulario del nombre del campo de la BBDD
			if (t.length!=2)
				{
				MiError('Error al definir campo relacionado: ' + tmp[i]);
				return;
				};
				
			campoForm=trim(t[0]);
			campoBBDD=trim(t[1]);
			ref=document.getElementsByName(campoForm);
			if (ref.length>0)
				{
				j=dat.length;
				dat[j]=new FRMUnCampoREL(campoBBDD,ref[0]); //Creamos instancia a la clase de 1 campo
				}
			else
				{
				MiError('El nombre de campo: ' + campoForm + ' no existe.');
				return;
				};
			};
			
		return dat;
	};


	//Cuando termina de cargar el IFRAME viene aqui
	function LoadCmp()
	{
	var i,valor,campo;
	var ifrDOC=cmpIFRAME.contentWindow.document;
	var j;
	
		EVTEliminaEvento(cmpIFRAME, 'load', LoadCmp); //Nos pulimos el evento para que no de por popa
		if (cmpREL==null)
			{
			clase.reading=false; //Se acabo la lectura
			return;	//No hay que rellenar nada
			};

		if (ifrDOC.getElementById('Status').innerHTML=='OK')
			{
			//La clave existia
			//Rellena los datos recibidos
			for (i=0;i<cmpREL.length;i++)
				{
				campo=cmpREL[i].cmpBBDD;						//Nombre del campo de la bbdd
				valor=ifrDOC.getElementById(campo).innerHTML;	//Leemos el valor recibido en el IFRAME	
				switch (cmpREL[i].objForm.tagName)
					{
					case 'INPUT':
					case 'TEXTAREA':
						cmpREL[i].objForm.value=valor;
						break;
						
					case 'SELECT':
						COMBOSelValue(cmpREL[i].objForm,valor);
						break;
					};
				
				/*
				Y ahora miramos si el campo relacionado tambien esta definido como clave
				Esto puede ser, para tener campos "autorellenables", en los que las claves y los campos a rellenar son los mismos
				pero que las claves tienen llamadas a busquedas del tipo >=, y por ejemplo el usuario le mete en el campo 
				"Juani" y el registro devuelve "Juanito". La clave ya no coincidiría con el valor en el campo, ya que se inicializo
				cuando se fue a leer como Juani, y ahora el objeto del formulario tendría Juanito.
				El metodo statusData devolvería null, como que no esta leida, y no es verdad
				*/
				for (j=0;j<cmpCLV.length;j++)
					if (cmpCLV[j].objForm==cmpREL[i].objForm) cmpCLV[j].valueIni=valor;
				};
					
			cmpFlagLectura=true; //Leida y correcta
			}
		else
			{
			//Hay error, pero ¿cual?
			if (ifrDOC.getElementById('NumeroError').innerHTML=='-12')
				{
				//La clave no existia,
				//borro todo
				for (i=0;i<cmpREL.length;i++)
					{
					switch (cmpREL[i].objForm.tagName)
						{
						case 'INPUT':
						case 'TEXTAREA':
							cmpREL[i].objForm.value='';
							break;
							
						case 'SELECT':
							cmpREL[i].objForm.selectedIndex=0;
							break;
						};
					};
					
				cmpFlagLectura=false; //Datos leidos pero erroneos
				}
			else	
				{	
				//Un error que no me esperaba
				t='Error no. ' + ifrDOC.getElementById('NumeroError').innerHTML + '\n';
				t+=ifrDOC.getElementById('DescripcionError').innerHTML + '\n';
				t+='Origen:' + ifrDOC.getElementById('Origen').innerHTML + '\n';
				MiError('Error al cargar select.\n'+ t);					
				};
			};
		
		//Se acabo la lectura
		clase.reading=false;
	};


	// Muestra un error de la clase
	 function MiError(txt)
	 {
		 alert(txt + '\nGrupo relaciones:' +	clase.name);
	 };

	/***************************************************************
				 Funciones métodos de la clase
	***************************************************************/

	/*
	Abre la ventana de mantenimiento para los datos relacionados
	*/
	function mtWinProcess(pagina,titulo)
	{
	var tit='';
	
		if (typeof(titulo)!='undefined') tit=titulo;

		DefineWin(); //Define la ventana si no lo estaba

		//Anadimos el parametro de window
		if (pagina.indexOf('?')==-1)
			pagina+='?window=S';
		else
			pagina+='&window=S';
			
		//y abre la ventana
		clase.win.Abre(pagina, tit);
	};

	/*
	Abre la ventana de busqueda para los datos relacionados
	*/
	function mtWinSearch(pagina,titulo)
	{
	var PBus='';
	var PCheck='';
	var check='';
	var tit='';
	var proceso;
	
		if (typeof(titulo)!='undefined') tit=titulo;

		DefineWin();	//Define la ventana si no lo estaba

		for (var i=0;i<cmpCLV.length;i++)
			{
			//Cadena de parametros para la ventana de la busqueda
			//PBus= campoFORM=micampo_regact&campoBBDD=micampo ...
			if (PBus!='') PBus+='&';
			PBus+='CampoFORM=' + cmpCLV[i].objForm.name;
			PBus+='&CampoBBDD=' + cmpCLV[i].cmpBBDD;
			
			//Cadena de parametros para la funcion de checking de cierre de la ventana
			//PCheck= ,'micampo_regact','valor' ...
			PCheck+=",'" + cmpCLV[i].objForm.name + "','" + cmpCLV[i].objForm.value + "'";
			};

		if (pagina.indexOf('?')!=-1)
			Proceso=pagina + '&' + PBus;
		else
			Proceso=pagina + '?' + PBus;
		
		//Abre la ventana
		clase.win.Abre(Proceso, tit);
		
		//Funcion de chequeo
		check+="FRMCheckSearchWin(";
		check+=" 'FRMCmpsREL[\"" + clase.name + "\"].win', ";			//Referencia a la ventana
		check+=" 'FRMCmpsREL[\"" + clase.name + "\"].readData();' "; 	//Funcion a ejecutar al finalizar
		check+=PCheck;	//La lista de campos / valores
		check+=");";
		
		eval( check );	//Y llama a la funcion de checkeo
	};


	/*
	Si se le pasa true reseta el status de lectura a true y copia los valores actuales de los campos a valueIni.
	esto se usa para indicarle a la clase desde fuera que los datos son correctos (por que los ha leido otro proceso, 
	o se han actualizado de otra manera.
	Si se le pasa false resetea el status de lectura a null e inicializa los valueIni a null. Esto se utiliza para indicarle
	a la clase que los datos de los campos los ha inicializado un proceso externo que sabe que no son correctos. (por ejemplo
	cuando se inicilizan para un alta nueva
	*/
	function mtResetStatus(flag)
	{
		if (flag)
			{
			cmpFlagLectura=true;
			for (var i=0;i<cmpCLV.length;i++)
				cmpCLV[i].valueIni=cmpCLV[i].objForm.value;
			}
		else
			{
			cmpFlagLectura=null;
			for (var i=0;i<cmpCLV.length;i++)
				cmpCLV[i].valueIni=null;
			};
	};

	/*
	Devuelve:
	true si los datos estan leidos y son correctos
	false si estan leidos, pero no son correctos 
	null si los datos no estan leidos
	*/
	function mtDataStatus()
	{
	var CamposChange=false;
	var i;
	
		//Primero vemos si los campos han cambiado
		for (i=0;i<cmpCLV.length;i++)
			{
			if (cmpCLV[i].valueIni!=cmpCLV[i].objForm.value)
				{
				cmpFlagLectura=null;
				break;
				};
			};
				
		//Y devolvemos el flag		
		return cmpFlagLectura;
	};


	//Lee los datos a través del SQL y actualiza los campos 
	function mtGetData()
	{
	var flag, dat='';
	
		//Miramos a ver si hay que leerlo
		if (clase.statusData()!=true)
			{
			//Estamos leyendo, pisha
			clase.reading=true;
			
			//Carga los valores para los datos clave en valueIni y se va montando el parámetro a pasar en el querystring
			for (var i=0;i<cmpCLV.length;i++)
				{
				cmpCLV[i].valueIni=cmpCLV[i].objForm.value;
				dat+='&' + cmpCLV[i].cmpBBDD + '=' + escape(cmpCLV[i].objForm.value);
				};
	
			//Asignamos el evento para que cuando cargue muestre los datos
			EVTAsignaEvento(cmpIFRAME, 'load', LoadCmp);
			//Y cargamos la página
			cmpIFRAME.src=cmpPag + dat;
			};
	};
	

	/*****************************************************************
				Clases internas de la clase
	*****************************************************************/
	
	//Clase para definir la matriz interna de campos de la clase FRMCamposREL
	function FRMUnCampoREL(CampoBBDD,Obj)
	{
		this.cmpBBDD=CampoBBDD;		//Nombre del campo en la BBDD
		this.objForm=Obj;			//Objeto del formulario principal
		this.valueIni=null;			//Valor inicial del objeto (asi sabemos cuando cambia)
	};	
};


/*****************************************************************************************




							 Clase para la gestion de los campos




******************************************************************************************/
function FRMCampo(cmp,param)
{
var clase=this;	//Referencia a nosotros mismos, para las funciones internas

	//Exportacion de métodos
	this.init=mtInicializa;				//Inicializa el campo con el valor por defecto
	this.validate=mtValida;				//Devuelve true o false segun sea valido o no el valor introducido en el campo
	this.loadListData=mtCargaCombo;		//Carga la lista de opciones a través de una página xxxxSQL.asp.(listDataAsp y listDataIdx)
	this.loadList=mtCargaComboList;		//Carga la lista de opciones que tenga defindia optList
	this.callFunction=mtFuncionTeclaAccion; 	//Fuerza la llamada a la funcion definida en functionPointer, con el evento a null
	this.statusChange=mtStatusChange;			//Devuelve true cmpOriginalValue != obj.value
	this.statusChangeReset=mtStatusChangeIni;	//Resetea el flag de cambio a false. (copia el value en cmpOriginalValue)
	this.format=mtFormat;	//Devuelve una cadena con el formato valido para el campo (para informar al usuario)
	this.enabled=mtEnabled; //Pone enabled / disabled el elemento asociado a la clase, según flag

	//Propiedades y sus valores por defecto. OJO, que despues de recibirlos, se ajustan para evitar incongruencias
	this.obj=cmp;
	this.type='string';
	this.chars=0;
	this.decimals=0;
	this.valueIni='';
	this.optList='';
	this.align='';				//El valor por defecto depende del tipo de dato
	this.letterCase='normal';
	this.signed=true;
	this.selected=true;
	this.required=false;
	this.valueMax=null;
	this.valueMin=null;
	this.charOnly=null;
	this.charInvalid=null;
	this.charAlphabetical=false;
	this.autoSize=true;
	this.size=null;
	this.tabIndex=1;
	this.functionKey=null;
	this.functionPointer=null;
	this.keyAv=null;
	this.keyRe=null;
	this.relationData=null;
	this.listDataAsp=null;
	this.listDataIdx=null;
	this.iclass=null;
	this.iclassFocus=null;
	this.iclassOver=null;
	this.iclassDisabled=null;

	RecogeParametros(param); 	//Analizamos y recogemos los parámetros
	AjusteDeParametros();		//Ajusta las incongruencias de los parametros y las excepciones

//Variables globales de la clase, que serán accesibles por todas las funciones internas de la clase y perdurarán
var cmpTipoObjeto=DameTipoObjeto(); 	//Cadena con el tipo de objeto asociado a la clase: INPUT.subtipo, TEXTAREA, SELECT
var cmpIFRAME=null;						//Referencia al IFRAME que se crea la clase cuando necesita llamar a SQLs externas
var cmpTimeFocus=0;						//Se usa para controlar cuando coge el foco por medio del click del ratón
var cmpOriginalValue=this.obj.value;	//Lleva el valor que tenía inicialemnete el campo, para controlar cambios


	//Propiedades calculadas
	this.maxLength=MaximoNumeroCaracteres(); //Máximo numero de caracteres que acepta el campo (0 máximo).

	//Verificación de objeto / tipo
	if (VerificaObjetoTipo()==-1) return -1;

	//Datos exportados
	this.obj.tabIndex=this.tabIndex;	//El tabindex
	if (this.align!=null) this.obj.style.textAlign=this.align; //Alineación

	//Tamaños
	if (this.autoSize) this.size=this.maxLength;
	if (this.size!=null) this.obj.size=this.size;

	//Maximo número de caracteres
	switch (cmpTipoObjeto)
		{
		//Si es un input, en su propiedad de maxima longitud
		case 'INPUT.text':
			this.obj.maxLength=this.maxLength;
			break;

		//Si es un textarea, la cosa cambia. Hay que controlarlo por evento
		case 'TEXTAREA':
			if (this.maxLength!=0)
				{
				//Asignamos el evento para su control
				EVTAsignaEvento(this.obj, 'keypress', evControlLongitudMaxima);
				};
		};

	//Relleno de listas
	if (this.listDataAsp) 
		mtCargaCombo();
	else
		if (this.optList!=null) mtCargaComboList();

	//Control de las clases
	if (this.iclass!=null) this.obj.className=this.iclass;
	if (this.iclassFocus!=null)
		{
		EVTAsignaEvento(this.obj, 'focus', evClass);
		EVTAsignaEvento(this.obj, 'blur', evClass);
		};
	if (this.iclassOver!=null)
		{
		EVTAsignaEvento(this.obj, 'mouseover', evClass);
		EVTAsignaEvento(this.obj, 'mouseout', evClass);
		};
	
	//Control de cuando coge el foco para el selected
	if (this.selected!=null)
		if (this.selected)
			{
			EVTAsignaEvento(this.obj, 'focus', evFocoSel);
			EVTAsignaEvento(this.obj, 'click', evFocoSel);
			}
		else
			{
			EVTAsignaEvento(this.obj, 'focus', evFocoNoSel);
			};


	//Control de cuando pierde el foco para leer un SQL
	if (this.relationData!=null) EVTAsignaEvento(this.obj, 'blur', evLeeSQL);

	//Control de teclas especiales
	//Teclas para función externa
	if (this.functionKey!=null && this.functionPointer!=null) EVTAsignaEvento(this.obj, 'keydown', evKeyFunction);
	//Teclas de avance / retroceso
	//El campo lo movemos con el keydown. (en firefox, aun parando el evento, si el elemento es un select,
	//recibe la pulsacion de la tecla)	
	if (this.keyAv!=null)
		{
		EVTAsignaEvento(this.obj, 'keydown', evKeyAv);
		EVTAsignaEvento(this.obj, 'keyup', evKeyAv);
		EVTAsignaEvento(this.obj, 'keypress', evKeyAv);
		};
		
	if (this.keyRe!=null) 
		{
		EVTAsignaEvento(this.obj, 'keydown', evKeyRe);
		EVTAsignaEvento(this.obj, 'keyup', evKeyRe);
		EVTAsignaEvento(this.obj, 'keypress', evKeyRe);
		};

	//Control de la entrada de caracteres por parte del usuario
	//el ucase / lcase	
	if (this.letterCase!=null)
		{
		switch (this.letterCase)
			{
				case 'ucase':
					EVTAsignaEvento(this.obj, 'keypress', evUcase);
					break;
				
				case 'lcase':
					EVTAsignaEvento(this.obj, 'keypress', evLcase);
					break;
			};
		};

	//Control de caracteres especiales
	if (this.charOnly!=null)
		{
		EVTAsignaEvento(this.obj, 'keypress', evCharOnly);
		}
	else
		{
		if (this.charInvalid!=null) EVTAsignaEvento(this.obj, 'keypress', evCharInvalid);
		if (this.charAlphabetical)  EVTAsignaEvento(this.obj, 'keypress', evCharAlphabetical);
		};

	//Asignación según tipo de su función de control de entrada de caracteres
	switch(this.type)
	{
		case 'number':
			EVTAsignaEvento(this.obj, 'keypress', evTypeNumber);
			break;
		case 'date':
			EVTAsignaEvento(this.obj, 'keypress', evTypeDate);
			break;
		case 'hour':
			EVTAsignaEvento(this.obj, 'keypress', evTypeHour);
			break;
		case 'mail':
			EVTAsignaEvento(this.obj, 'keypress', evTypeMail);
			break;
		case 'web':
			EVTAsignaEvento(this.obj, 'keypress', evTypeWeb);
			break;
		case 'hexa':
			EVTAsignaEvento(this.obj, 'keypress', evTypeHexa);
			break;
	};




	/*****************************************************************
	
				 FUNCIONES INTERNAS DE LA CLASE


	*****************************************************************/
	
	/*********************************************************************************
	Funcion para recoger los parámetros
	*********************************************************************************/
	function RecogeParametros(prop)
	{
		if (prop.length==0) return;
		
	//Array referenciado por los nombres de las propiedades que se le pueden pasar en la cadena de parametros
	//0-cadena, 1-numero, 2-bool, 3-cadena opcional 10-opcion, 11-lista teclas
	var propiedades=new Array();
		propiedades['type']=[10,'string,number,memo,date,hour,list,bool,mail,web,hexa'];
		propiedades['chars']=[1];
		propiedades['decimals']=[1];
		propiedades['valueIni']=[3];
		propiedades['optList']=[0];
		propiedades['align']=[10,'left,rigth,center,justify'];
		propiedades['letterCase']=[10,'normal,ucase,lcase'];
		propiedades['signed']=[2];
		propiedades['selected']=[2];
		propiedades['required']=[2];
		propiedades['valueMax']=[3];
		propiedades['valueMin']=[3];
		propiedades['charOnly']=[0];
		propiedades['charInvalid']=[0];
		propiedades['charAlphabetical']=[2];
		propiedades['autoSize']=[2];
		propiedades['size']=[1];
		propiedades['tabIndex']=[1];
		propiedades['functionKey']=[11];
		propiedades['functionPointer']=[0];
		propiedades['keyAv']=[11];
		propiedades['keyRe']=[11];
		propiedades['relationData']=[0];
		propiedades['listDataAsp']=[0];
		propiedades['listDataIdx']=[1];
		propiedades['iclass']=[0];
		propiedades['iclassFocus']=[0];
		propiedades['iclassOver']=[0];
		propiedades['iclassDisabled']=[0];
		
	var p,j,i,lis,flag;
	var miprop,propNombre,propValor;

		p=prop.split(';');			//Me creo un array con los grupos de propiedad:valor
		for (i=0;i<p.length;i++)	//Me lo recorro
			{
			//Separamos el nombre de la propiedad de su valor
			j=p[i].indexOf(':');	//Buscamos dentro del grupo el primer caracter ':'	
			if (j==-1)
				{
				//Esa propiedad no tiene valor
				propNombre=trim(p[i]);
				propValor='';
				}
			else
				{
				//La propiedad hasta los ':' y a partir de ahí, el valor de la misma
				propNombre=trim(p[i].substr(0,j));
				propValor=p[i].substr(j+1);
				};
					
			//Buscamos la propiedad en la matriz
			if (typeof propiedades[propNombre]!='undefined')
				{
					//Le asigno a miprop la referencia a la propiedad de la clase, que me monto a partir del parametro
					miprop='clase.' + propNombre;
					//Dependiendo del tipo de propiedad
					switch (propiedades[propNombre][0])
						{
						//Asignacion de una cadena
						case 0:
							if (propValor=='')
								MiError('La propiedad ' + propNombre + ' debe contener una cadena.');
							else
								{
								//Sustituimos las  " por \" para que no le afecte al eval									
								propValor=propValor.replace(/"/g,'\\\"');	
								eval( miprop + ' = "' + propValor + '";' );
								};
							break;
							
						//Asignacion de un numero entero
						case 1:
							if (isNaN(propValor))
								MiError('La propiedad ' + propNombre + ' debe ser numerica.');
							else
								eval( miprop + '=parseInt("' + propValor + '",10);' );
							break;
						
						//Asignacion de un booleano
						case 2:
							propValor=trim(propValor.toLowerCase());	//el valor a minusculas y le quitamos los espacios
							if (propValor!='true' && propValor!='false')
								MiError('La propiedad ' + propNombre + ' debe ser true o false.');
							else
								eval( miprop + '=' + propValor + ';' );
							break;
							
						//Asignacion de cadena opcional
						case 3:
							propValor=propValor.replace(/"/g,'\\\"');	//Sustituimos las  " por \" para que no le afecte al eval
							eval( miprop + ' = "' + propValor + '";' );
							break;
							
						//Asignacion de una cadena con un valor preestablecido
						case 10:
							lis=propiedades[propNombre][1].split(',');	//Nos monatamos una matriz con las opciones válidas
							propValor=trim(propValor);	//Le quitamos los espacios al valor por si acaso
							flag=false;
							for (j=0;j<lis.length;j++)	//Buscamos en la lista a ver si la propiedad existe
								if (lis[j]==propValor)
									{
									flag=true;
									j=lis.length; //Nos salimos del bucle
									};
							if (flag)
								eval( miprop + ' = "' + propValor + '";' );	//Asignamos
							else
								MiError('La propiedad ' + propNombre + ' debe ser uno de estos valores:\n' + propiedades[propNombre][1]);
							break;

						//Secuencia de teclas
						case 11:
							eval( miprop + ' = "' + propValor + '";' );
							break;	
						};
				}
			else
				MiError('Propiedad ' + propNombre + ' no valida.');
			};
	};
	
	/****************************************************************************************************************
	 Ajusta las incongruencias recibidas asi como las excepciones, para dejar las propiedades de la clase niqueladas
	 Se la llama justo despues de analizar los parametros.
	 Aquellas propiedades que no vaya a usar las pone a null.
	****************************************************************************************************************/
	function AjusteDeParametros()
	{
		//Excepciones expecificas de cada tipo
		switch (clase.type)
			{
			case 'string':
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				if (clase.charOnly!=null)	//Prevalece charonly sobre las otras dos. Las otras dos son complementarias
					{
					clase.charInvalid=null;
					clase.charAlphabetical=null;
					};
				if (clase.chars==0 || clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				break;
				
			case 'number':
				clase.optList=null;
				clase.letterCase=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.chars==0 || clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='right';
				break;

			case 'memo':
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				if (clase.charOnly!=null)	//Prevalece charonly sobre las otras dos. Las otras dos son complementarias
					{
					clase.charInvalid=null;
					clase.charAlphabetical=null;
					};
				clase.autoSize=null;
				clase.size=null;
				clase.listData=null;
				if (clase.align=='') clase.align='justify';
				break;
				
			case 'date':
				clase.chars=10;
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				clase.letterCase=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				
				//Buscamos comandos dentro del valueIni, valueMax y valueMin
				clase.valueIni=RedefineDate(clase.valueIni);
				clase.valueMax=RedefineDate(clase.valueMax);
				clase.valueMin=RedefineDate(clase.valueMin);
				break;
				
			case 'hour':
				clase.chars=8;
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				clase.letterCase=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				
				//Buscamos comandos dentro del valueIni, valueMax y valueMin
				clase.valueIni=RedefineHour(clase.valueIni);
				clase.valueMax=RedefineHour(clase.valueMax);
				clase.valueMin=RedefineHour(clase.valueMin);
				break;
			
			case 'list':
				clase.chars=0;
				clase.decimals=null;
				clase.signed=null;
				clase.align=null;
				clase.letterCase=null;
				clase.selected=null;
				clase.required=null;
				clase.valueMax=null;
				clase.valueMin=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				clase.autoSize=null;
				clase.size=null;
				if (clase.listData) clase.optList=null;
				break;
			
			case 'bool':
				clase.chars=0;
				clase.decimals=null;
				clase.signed=null;
				clase.optList=FRMTextos[16];	//SI$Si|NO$No Le forzamos la lista value=SI/NO texto=Si/No
				clase.align=null;
				clase.letterCase=null;
				clase.selected=null;
				clase.required=null;
				clase.valueMax=null;
				clase.valueMin=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				clase.autoSize=null;
				clase.size=null;
				if (clase.listData) clase.optList=null;
				if (clase.valueIni=='1' || clase.valueIni.toUpperCase()=='TRUE') clase.valueIni='SI';
				if (clase.valueIni=='0' || clase.valueIni.toUpperCase()=='FALSE') clase.valueIni='NO';
				break;
			
			case 'mail':
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				clase.letterCase=null;
				clase.valueMax=null;
				clase.valueMin=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.chars==0 || clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				break;
				
			case 'web':
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				clase.letterCase=null;
				clase.valueMax=null;
				clase.valueMin=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.chars==0 || clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				break;
				
			case 'hexa':
				clase.decimals=null;
				clase.signed=null;
				clase.optList=null;
				clase.charOnly=null;
				clase.charInvalid=null;
				clase.charAlphabetical=null;
				if (clase.chars==0 || clase.size!=null) clase.autoSize=false;
				clase.listData=null;
				if (clase.align=='') clase.align='left';
				break;
			};
			
		//Comprobacion de que los valores ini, max y min sean correctos para el campo
		if (clase.valueIni!=null && clase.valueIni!='')
			{
			if (!CompruebaValor(clase.valueIni)) 
				{
				clase.valueIni=null;
				MiError('valueIni no valido');
				};
			};
				
		if (clase.valueMax!=null) 
			{
			if (!CompruebaValor(clase.valueMax)) 
				{
				clase.valueMax=null;
				MiError('valueMax no valido');
				};
			};
				
		if (clase.valueMin!=null) 
			{
			if (!CompruebaValor(clase.valueMin))
				{
				clase.valueMin=null;
				MiError('valueMin no valido');
				};
			};
	};

	/****************************************************************************
	Verifica que el objeto asignado a la clase y el tipo de esta, sean correctos
	****************************************************************************/
	function VerificaObjetoTipo()
	{
	// Nos montamos un array referenciado por los valores de la propiedad type, y poniendo que TAGs son validos		
	var Tipo=new Array(); 
		Tipo['string']='INPUT.text TEXTAREA';
		Tipo['number']='INPUT.text';
		Tipo['memo']='TEXTAREA';
		Tipo['date']='INPUT.text';
		Tipo['hour']='INPUT.text';
		Tipo['list']='SELECT';
		Tipo['bool']='SELECT';
		Tipo['mail']='INPUT.text';
		Tipo['web']='INPUT.text';
		Tipo['hexa']='INPUT.text';

		//Buscamos ese tag en la lista que tiene cada type
		if (Tipo[clase.type].indexOf(cmpTipoObjeto)==-1)
			{
			MiError('El objeto asignado no es valido.\nPara type=' + clase.type + ' solo ' + Tipo[clase.type]);
			return -1;
			};
			
		return 0;
	};

	/**************************************************************************************************
	 Devuelve un entero con el máximo numero de caracteres que puede tener el campo, en base a su tipo
	**************************************************************************************************/
	function MaximoNumeroCaracteres()
	{
		return clase.chars + ((clase.decimals!=null && clase.decimals>0)?clase.decimals+1:0) + (clase.signed!=null && clase.signed?1:0);
	};

	/*******************************************************************************
	 Devuelve una cadena con el tipo de objeto que esta asociado a la clase:
	 INPUT.subtipo SELECT TEXTAREA
	*******************************************************************************/
	function DameTipoObjeto()
	{
	var CadTip;
	
		CadTip=clase.obj.tagName;
		if (clase.obj.tagName=='INPUT') CadTip+='.' + clase.obj.type.toLowerCase(); //Si es un input le metemos el tipo
		return CadTip;
	};

	/********************************************************************************
	 Se le pasa el objeto evento y devuelve la referencia a la clase que lo provoco
	********************************************************************************/
	function DameClaseDesdeEvento(evt)
	{
	var obj=EVTDimeObjeto(evt); //Miramos el campo que provoco el evento
		return FRMCmps[obj.name];	//Y lo referenciamos por el nombre directamente desde el array :)
	};
	
	
	/***************************************************************************************
	 Busca y sustituye comandos de fecha en valor y los sustituye devolviendo el resultado
		now			Fecha actual
		yearNow		Año actual
		monthNow	Mes actual
		dayNow		Día actual
	***************************************************************************************/
	function RedefineDate(valor)
	{
	var f,d,m,y,fc,res;
	var REnow=/now/g;
	var REyearNow=/yearNow/g;
	var REmonthNow=/monthNow/g;
	var REdayNow=/dayNow/g;
	
		if (valor=='' || valor==null) return valor;
		
		//Me monto 4 variables fc, d, m, a que son cadenas con la fecha actual, dia, mes y ano
		f=new Date();
		y=new String(f.getFullYear());
		m=new String(f.getMonth() + 1);
		if (m.length==1) m='0' + m;
		d=new String(f.getDate());
		if (d.length==1) d='0' + d;
		fc=d + '/' + m + '/' + y;

		//Y ahora voy sustituyendo
		res=valor;
		res=res.replace(REdayNow,d);
		res=res.replace(REmonthNow,m);
		res=res.replace(REyearNow,y);
		res=res.replace(REnow,fc);

		//Y devuelvo
		return res;
	};

	/***************************************************************************************
	 Busca y sustituye comandos de hora en valor y los sustituye devolviendo el resultado
		now			Hora actual completa
		hourNow		Hora actual
		minuteNow	Minuto actual
		secondNow	Segundo actual
	***************************************************************************************/
	function RedefineHour(valor)
	{
	var f,h,m,s,hc,res;
	var REnow=/now/g;
	var REhourNow=/hourNow/g;
	var REminuteNow=/minuteNow/g;
	var REsecondNow=/secondNow/g;
	
		if (valor=='' || valor==null) return valor;
		
		//Me monto 4 variables fc, d, m, a que son cadenas con la fecha actual, dia, mes y ano
		f=new Date();
		h=new String(f.getHours());
		if (h.length==1) h='0' + h;
		m=new String(f.getMinutes() + 1);
		if (m.length==1) m='0' + m;
		s=new String(f.getSeconds());
		if (s.length==1) s='0' + s;
		hc=h + ':' + m + ':' + s;

		//Y ahora voy sustituyendo
		res=valor;
		res=res.replace(REhourNow,h);
		res=res.replace(REminuteNow,m);
		res=res.replace(REsecondNow,s);
		res=res.replace(REnow,hc);

		//Y devuelvo
		return res;
	};
	
	
	/*************************************************************
	 Comprueba si valor es un dato válido para el tipo de input
	 Devuelve true si lo es, o false si no es correcto
	*************************************************************/
	function CompruebaValor(valor)
	{
		//Si se pasa de longitud
		if (clase.maxLength!=0 && valor.length>clase.maxLength) return false;
		
		//Y ahora dependiendo del tipo
		switch (clase.type)
		{
			//Número
			case 'number':
				return FRMChekNumber(valor,clase.chars,clase.decimals,clase.signed);
			//Fecha				
			case 'date':
				return FRMChekDate(valor);
			//Hora
			case 'hour':
				return FRMCheckHour(valor);
			//Mail
			case 'mail':
				return FRMCheckMail(valor);
			//Direccion web
			case 'web':
				return FRMCheckWeb(valor);
			//Hexa
			case 'hexa':
				return FRMCheckHexa(valor);
			//String y memo
			case 'string':
			case 'memo':
				//charOnly, charInvalid y charAlphabetical
				if (clase.charOnly!=null)
					return FRMCheckCharOnly(valor,clase.charOnly);
				else
					{
					if (clase.charInvalid!=null)
						{
						if (!FRMCheckCharInvalid(valor,clase.charInvalid))	return false;
						};
						
					if (clase.charAlphabetical)
						{
						if (!FRMCheckAlphabetical(valor)) return false;
						};
					return true;
					};
				
			//Los que no tienen comprobacion
			case 'list':
			case 'bool':
				return true;
		};
	};
	
	/*********************************************************************************
		Crea un IFRAME para poderse comunicar con la BBDD
	*********************************************************************************/
	function CreaIFRAME()
	{
		if (cmpIFRAME==null)
			{
			cmpIFRAME=document.createElement('IFRAME');
			cmpIFRAME.style.position='absolute';
			cmpIFRAME.style.visibility='hidden';
			document.body.appendChild(cmpIFRAME);
			};
	};

	/**********************************************************************************
	 Cuando ha terminado de cargar en el IFRAME la llamada a la página de carga de la 
	 lista del combo viene aqui
	**********************************************************************************/
	function CargaListaCombo()
	{
	var ifrDOC=cmpIFRAME.contentWindow.document;
	var t;
	
		EVTEliminaEvento(cmpIFRAME, 'load', CargaListaCombo);	//Le quitamos el evento para que no de por popa
		if (ifrDOC.getElementById('Status').innerHTML=='OK')
			{
			clase.optList=ifrDOC.getElementById('lista').innerHTML;
			COMBORellena(clase.obj,clase.optList);
			}
		else
			{
			t='Error no. ' + ifrDOC.getElementById('NumeroError').innerHTML + '\n';
			t+=ifrDOC.getElementById('DescripcionError').innerHTML + '\n';
			t+='Origen:' + ifrDOC.getElementById('Origen').innerHTML + '\n';
			MiError('Error al cargar select.\n'+ t);
			return -1;
			};
	};


	/****************************************************************
	 Llama a la funcion definida en functionPointer(si la hay)
	*****************************************************************/
	function LlamaAFunction(evt)
	{
		if (clase.functionPointer!=null)
			eval(clase.functionPointer + '(clase,evt);');
		else
			MiError('No tiene definida la propiedad functionPointer');
	};


	/**********************************************************************************************
	 Compara el valor pasado (siempre es una cadena) con el valor del campo
	 Devuelve -1 si el valor del campo es menor que el pasado
	 Devuelve 0 si son iguales
	 Devuelve 1 si el valor del campo es mayor que el pasado
	 A esta función la llama la de comprobación (validate), por lo que sólo llega para los
	 tipos string,number,hour,date,hexa,memo y ya esta comprobado que el campo contenga un valor 
	 válido del tipo de campo
	***********************************************************************************************/
	function ComparaValor(valor)
	{
	var ValueCmp,Value,t;
	var mat,d,m,a,s,h;
	
		switch(clase.type)
		{
			case 'string':
			case 'memo':
				ValueCmp=clase.obj.value;
				Value=valor;
				break;
				
			case 'number':
				t=clase.obj.value;
				ValueCmp=new Number(t.replace(/,/g,'.'));	//Le reemplazamos la , decimal por el .
				Value=new Number(valor.replace(/,/g,'.'));		//Le reemplazamos la , decimal por el .
				break;
				
			case 'hexa':
				ValueCmp=new Number('0x' + clase.obj.value);
				Value=new Number('0x' + valor);
				break;
				
			case 'hour':
				t=clase.obj.value;
				mat=t.split(':');
				h=parseInt(mat[0],10); m=parseInt(mat[1],10); s=parseInt(mat[2],10);
				ValueCmp=new Date(2000,7,1,h,m,s);
				
				mat=valor.split(':');
				h=parseInt(mat[0],10); m=parseInt(mat[1],10); s=parseInt(mat[2],10);
				Value=new Date(2000,7,1,h,m,s);
				break;
				
			case 'date':
				t=clase.obj.value;
				mat=t.split('/');
				d=parseInt(mat[0],10); m=parseInt(mat[1],10)-1;	a=parseInt(mat[2],10);
				ValueCmp=new Date(a,m,d,0,0,0);
				
				mat=valor.split('/');
				d=parseInt(mat[0],10); m=parseInt(mat[1],10)-1;	a=parseInt(mat[2],10);
				Value=new Date(a,m,d,0,0,0);
				break;
		};


	//Y ahora comparo y devuelvo
	if (ValueCmp<Value)
		return -1;
	else
		{
		if (ValueCmp>Value)
			return 1;
		else
			return 0;
		};
	};


	/*********************************************************************************
	 Muestra un error de la clase
	*********************************************************************************/
	 function MiError(txt)
	 {
		 alert(txt + '\nCampo:' +	clase.obj.name);
	 };

	
	 
	/*****************************************************************
	
					 EVENTOS INTERNOS DE LA CLASE

	*****************************************************************/
	 
	
	//Control de las clases cuando coge o suelta el foco
	function evClass(evt)
	{
		evt=EVTDimeEvento(evt);	//Cogemos la referencia valida al evento dependiendo del navegador
		
		//Pierde el foco o el ratón se sale de encima
		if (evt.type=='blur' || evt.type=='mouseout')
			if (clase.iclass!=null)
				clase.obj.className=clase.iclass;
			else
				clase.obj.className='';
		
		//Coge el foco
		if (evt.type=='focus' && clase.iclassFocus!=null)
				clase.obj.className=clase.iclassFocus;
				
		//Raton encima
		if (evt.type=='mouseover' && clase.iclassOver!=null)
				clase.obj.className=clase.iclassOver;
	};
	
	// Control del máximo numero de caracteres en un TEXTAREA.	 Lo dispara el keypress
	function evControlLongitudMaxima(evt)
	{
	var miclase=DameClaseDesdeEvento(evt);
	
		evt=EVTDimeEvento(evt);	//Cogemos la referencia valida al evento dependiendo del navegador
		if (miclase.obj.value.length >= miclase.maxLength)
			{
			//Ya no puede meter más caracteres. Verificamos si la tecla pulsada tiene ascii
			t=EVTDimeTecla(evt,'ASCII');
			if (t!=0)
				EVTNoPropagarEvento(evt); //Tiene ascii, así que no la dejamos pasar
			};
	};


	// Control de la secuencia de teclas para la llamada a la funcion externa. La dispara el keydown
	function evKeyFunction(evt)
	{
		if (FRMVerificaClick(clase.functionKey,evt))
			{
			LlamaAFunction(evt);
			EVTNoPropagarEvento(evt); //Las teclas nos valían, aqui se quedan
			};
	};


	//Control de las teclas de avance. La dispara keydown
	function evKeyAv(evt)
	{
		if (FRMVerificaClick(clase.keyAv,evt))
			{
			EVTNoPropagarEvento(evt);
			if (evt.type=='keydown') FRMFocoSig(clase.obj);
			};
	};

	//Control de las teclas de retroceso. La dispara keydown
	function evKeyRe(evt)
	{
		if (FRMVerificaClick(clase.keyRe,evt))
			{
			EVTNoPropagarEvento(evt);
			if (evt.type=='keydown') FRMFocoAnt(clase.obj);
			};
	};
	

	//Control de que todo va en mayusculas. La dispara keypress
	function evUcase(evt)
	{
	var nt,t;

		t=String.fromCharCode(EVTDimeTecla(evt,'ASCII'));	//Creo una cadena con la tecla pulsada
		nt=t.toUpperCase();									//la paso a mayusculas
		if (nt!=t) EVTCambiaASCII(evt,nt.charCodeAt(0));	//Y la fuerzo
	};
	
	//Control de que todo va en minusculas. La dispara keypress
	function evLcase(evt)
	{
	var nt,t;

		t=String.fromCharCode(EVTDimeTecla(evt,'ASCII'));	//Creo una cadena con la tecla pulsada
		nt=t.toLowerCase();									//la paso a minusculas
		if (nt!=t) EVTCambiaASCII(evt,nt.charCodeAt(0));	//Y la fuerzo
	};
	
	//Control de que selccione el texto cuando coge el foco. Lo dispara focus y el click.
	function evFocoSel(evt)
	{
	var d=new Date();
	var a;

		switch (evt.type)
			{
			case 'focus':
				//Acaba de coger el foco. Inicializo el tiempo
				cmpTimeFocus=d.getTime();
				clase.obj.select();
				break;
				
			case 'click':
				//Un click de ratón. ¿hace cuanto que cogio el foco?. 
				//Si hace menos de 100 mlsegundo, el foco lo coge por el ratón, y lo vuelvo a seleccionar
				a=d.getTime();
				a-=cmpTimeFocus;
				if ( a < 100)
					{
					clase.obj.select();
					EVTNoPropagarEvento(evt);
					return false;
					};
				break;
			};

		return true;
	};              

	//Control de que deseleccione el texto cuando coge el foco. Lo dispara focus
	function evFocoNoSel(evt)
	{
		if (OBJQueNavegador()=='IE')
			clase.obj.focus();	//Con el TAB no funciona @@@
		else
			setTimeout('FRMFocoNoSelect("' + clase.obj.name + '")',0);
	};
	
	
	//Control de la propiedad charonly. La dispara el keypress
	function evCharOnly(evt)
	{
	var t,c;
	var cad=clase.charOnly;
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no esta, asi que no pasa
			};
	};

	//Control de la propiedad charinvalid. La dispara el keypress
	function evCharInvalid(evt)
	{
	var t,c;
	var cad=clase.charInvalid;
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)!=-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron esta, asi que no pasa
			};
	};

	//Control de la propiedad charalphabetical. La dispara el keypress
	function evCharAlphabetical(evt)
	{
	var t,c;
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (!FRMCheckAlphabetical(t)) EVTNoPropagarEvento(evt);
			};
	};

	//funcion de control de entrada de type=number. La dispara el keypress
	function evTypeNumber(evt)
	{
	var t,c;
	var cad='0123456789';
		if (clase.signed) cad+='-';
		if (clase.decimals>0) cad+=',.';
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			if (c==46)
				{
				//Controlamos que el . se cambie por ,
				EVTCambiaASCII(evt,44);
				};
				
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no esta, asi que no pasa
			};
	};
	
	//funcion de control de entrada de type=date. La dispara el keypress
	function evTypeDate(evt)
	{
	var t,c;
	var cad='0123456789/';
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			if (c==45)
				{
				//Controlamos que el - se cambie por /
				EVTCambiaASCII(evt,47);
				return;
				};
				
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no esta, asi que no pasa
			};
	};
	
	//funcion de control de entrada de type=hour. La dispara el keypress
	function evTypeHour(evt)
	{
	var t,c;
	var cad='0123456789:';
		
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no esta, asi que no pasa
			};
	};
	
	//funcion de control de entrada de type=mail. La dispara el keypress
	function evTypeMail(evt)
	{
	var t,c;
	var cad='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz@.-_';
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no está, asi que no pasa
			};		
	};
	
	//funcion de control de entrada de type=web. La dispara el keypress
	function evTypeWeb(evt)
	{
	var t,c;
	var cad='0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz:/.-';
	
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (cad.indexOf(t)==-1) EVTNoPropagarEvento(evt);	//El ascii que pulsaron no está, asi que no pasa
			};
	};
	
	//funcion de control de entrada de type=hexa. La dispara el keypress
	function evTypeHexa(evt)
	{
	var t,c;
		
		c=EVTDimeTecla(evt,'ASCII');
		if (c!=0)
			{
			t=String.fromCharCode(c);	//Creo una cadena con la tecla pulsada
			if (!FRMCheckHexa(t)) EVTNoPropagarEvento(evt);
			};
	};

	//función para leer un SQL de datos relacionados. La dispara el blur
	function evLeeSQL(evt)
	{
		FRMCmpsREL[clase.relationData].readData();
	};


	/*****************************************************************
	
			 FUNCIONES INTERNAS DE LA CLASE PARA LOS METODOS

	*****************************************************************/
	//Pone enabled / disabled el elemento asociado a la clase, según flag
	function mtEnabled(flag)
	{
		if (flag)
			{
			//Enabled
			clase.obj.disabled=false;
			if (clase.iclass!=null)
				clase.obj.className=clase.iclass;
			else
				clase.obj.className='';
			}
		else
			{
			//Disabled
			clase.obj.disabled=true;
			if (clase.iclassDisabled!=null) clase.obj.className=clase.iclassDisabled;
			};
	};
	
	//Devuelve una cadena de texto para poder informar al usuario del formato del campo
	function mtFormat()
	{
	var t='',c='';
	
		switch (clase.type)
		{
			//Número
			case 'number':
				
				if (clase.signed) c+='-';
				t=FRMTextos[8] + ' ' + clase.chars + ' ' + FRMTextos[9]; //Número de xxx digitos
				c+=string('n',clase.chars);
				if (clase.decimals)
					{
					c+=',' + string('n',clase.decimals);
					t+=' ' + FRMTextos[10] + ' ' + clase.decimals + ' ' + FRMTextos[11];	//y xxx decimales máximo
					};
				if (clase.signed) t+=' ' + FRMTextos[12];	//con signo opcional
				return t + '\n(' + c + ')';

				
			//Fecha				
			case 'date':
				return FRMTextos[14];
				
			//Hora
			case 'hour':
				return FRMTextos[15];
				
			//Mail
			case 'mail':
				return FRMTextos[6];
				
			//Direccion web
			case 'web':
				return FRMTextos[7];

			//Los que no tienen texto
			case 'hexa':
			case 'string':
			case 'memo':
			case 'list':
			case 'bool':
				return '';
		};
	};
	
	//Inicializa el campo con su valor por defecto
	function mtInicializa()
	{
		switch (cmpTipoObjeto)
			{
			case 'INPUT.text':
			case 'TEXTAREA':
				if (clase.valueIni!=null)
					clase.obj.value=clase.valueIni;
				break;
				
			case 'SELECT':
				if (clase.valueIni!=null)
					COMBOSelValue(clase.obj,clase.valueIni);
				break;
			};
	};
	
	
	//Devuelve true o false segun sea valido o no el valor introducido en el campo
	function mtValida()
	{
		switch (cmpTipoObjeto)
			{
			case 'INPUT.text':
			case 'TEXTAREA':
				//Comprobamos si es requerido y esta vacio
				if (clase.required && clase.obj.value=='')
					{
					alert(FRMTextos[0]); //'El campo no puede estar vacío.'
					return false;
					};
				
				//Las comprobaciones si es requerido o el campo no esta vacio
				if (clase.required || clase.obj.value!='')
					{
					//Comprobamos que lo que tenga el input sea del tipo correcto
					if (CompruebaValor(clase.obj.value))
						{
						//y comprobamos máximos y minimos
						if (clase.valueMin!=null)
							if (ComparaValor(clase.valueMin)==-1)
								{
								alert(FRMTextos[1] + ' ' + clase.valueMin); //'El valor introducido debe ser mayor o igual a' 
								return false;
								};
								
						if (clase.valueMax!=null)
							if (ComparaValor(clase.valueMax)==1)
								{
								alert(FRMTextos[2] + ' ' + clase.valueMax); //'El valor introducido debe ser menor o igual a '
								return false;
								};
						}
					else
						{
						//'El valor introducido en el campo no es correcto. Formato valido xxxxx'
						alert(FRMTextos[3] + '\n' + FRMTextos[13] + ' ' +clase.format()); 
						return false;
						};
					};
				break;
				
			case 'SELECT':
				break;
			};
			
		//Validaciones contra la bbdd
		if (clase.relationData!=null)
			{
			if (FRMCmpsREL[clase.relationData].reading) return null; //La relación esta ocupada leyendo un dato
			if (FRMCmpsREL[clase.relationData].statusData()!=true)
				{
				alert(FRMTextos[4]); //'El valor de este campo debe existir en la base de datos.'
				return false;
				};
			};
			
		//Todo OK
		return true;
	};
	
	//Carga un combo a partir de una página xxxxSQL.asp. listDataAsp lleva la página y listDataIdx el numero de lista
	function mtCargaCombo()
	{
	var pag;
		
		if (clase.type=='list' || clas.type=='bool')
			{
			if (clase.listDataAsp)
				{
				pag=clase.listDataAsp + '?Tipo=O&Action=';	//Me monto la página con las opciones
				if (clase.listDataIdx!=null) pag+=clase.listDataIdx;
	
				CreaIFRAME();	//Si no estaba creado, lo crea
				EVTAsignaEvento(cmpIFRAME, 'load', CargaListaCombo);	//Asignamos el evento para cuando cargue el IFRAME
				cmpIFRAME.src=pag;	//Y cargamos la peich
				}
			else
				{
				MiError('No tiene definida la propiedad listDataAsp');
				return -1;
				};
			}
		else
			{
			MiError('Este campo no permite la carga de opciones a traves de una pagina xxxxxSQL.asp');
			return -1;
			};
	};
	
	//Carga en el combo la lista que haya en optList
	function mtCargaComboList()
	{
		if (clase.optList!=null && (clase.type=='list' || clase.type=='bool'))
			COMBORellena(clase.obj,clase.optList);
		else
			{
			MiError('Este campo no es tipo list u optList es null');
			return -1;
			};			
	};
	
	
	//Fuerza la llamada a la funcion definida en functionPointer, pero con el evento a null
	function mtFuncionTeclaAccion()
	{
		LlamaAFunction(null);
	};
	
	//Devuelve true si el contenido del campo cambio
	function mtStatusChange()
	{
		if (cmpOriginalValue!=clase.obj.value)
			return true;
		else
			return false;
	};
	
	//Inicializa el valor original del campo, para controlar cambios
	function mtStatusChangeIni()
	{
		cmpOriginalValue=clase.obj.value;
	};
};

/****************************************************************** FIN DE LA CLASE FRMCampo ************************************/



/********************************************************
	 Crea y muestra la ventana de espera
*********************************************************/
function FRMAlert(txt,mls)
{
var w=document.getElementById('WinEspera'); //referencia al div de la ventana;
var html='';

	if (w==null)
		{
		//No existe, hay que crearla
		w=document.createElement('DIV');
		w.id='WinEspera';
		w.style.visibility='hidden';
		w.style.position='absolute';
		w.style.overflow='hidden';
		w.style.zIndex=99;
		document.body.appendChild(w); //Y se lo añadimos al documento
		};

	//Le metemos el texto dentro de una tabla
	html+='<table>';
	html+='<tr><td id="WinEsperaCell">';
	html+=txt + '<br>|' + '</td></tr></table>';
	w.innerHTML=html;

	//Centramos alto y ancho
	if (OBJQueNavegador()=='IE')
		{
		Top=Math.round(document.body.scrollTop + (document.body.clientHeight/2) - (w.offsetHeight/2));
		Left=Math.round(document.body.scrollLeft + (document.body.clientWidth/2) - (w.offsetWidth/2));
		}
	else
		{
		Top=Math.round(document.body.scrollTop + (window.innerHeight/2) - (w.offsetHeight/2));
		Left=Math.round(document.body.scrollLeft + (window.innerWidth/2) - (w.offsetWidth/2));
		};
		
	//Se lo asignamos
	w.style.top=Top + 'px';
	w.style.left=Left + 'px';


	//Y la sacamos a relucir
	if (typeof(mls)!='undefined')
		setTimeout('FRMSacaAlert()',mls);
	else
		FRMSacaAlert();
};

//Pone visible la ventana siempre que tenga contenido y se autollama para hacer la animacion
function FRMSacaAlert()
{
var w=document.getElementById('WinEspera');
var td,t,c;

	if (w!=null && w.innerHTML!='')
		{
		w.style.visibility='visible';
		
		//Animación
		td=document.getElementById('WinEsperaCell');
		t=td.innerHTML;
		c=t.substr(t.length-1,1);
		switch(c)
			{
			case '|':
				c='/';
				break;
			
			case '/':
				c='-';
				break;
			
			case '-':
				c='\\';
				break;
			
			case '\\':
				c='|';
				break;
			};

		t=t.substr(0,t.length-1) + c;
		td.innerHTML=t;
		
		//Me vuelvo a llamar
		setTimeout('FRMSacaAlert()',500);
		};
};

/*********************************************************
	 Oculta la ventana de espera
*********************************************************/
function FRMAlertHidden()
{
var w=document.getElementById('WinEspera'); //referencia al div de la ventana;	
	
	if (w!=null) 
		{
		w.innerHTML='';	
		w.style.visibility='hidden';
		};
};

/****************************************************************
 Devuelve true si la ventana de espera esta visible, false si no
****************************************************************/
function FRMAlertVisible()
{
var w=document.getElementById('WinEspera');
var f=false;

	if (w!=null)
		if (w.style.visibility=='visible') f=true;
	
	return f;
};


/********************************************************************************************
 Se le pasa un nombre de campo y lo deselecciona borrando el contenido y volviendolo a poner
 A esta función la llama el evento focus desde la clase FRMCampo con un timeout, para darle
 tiempo a acabar el focus(). Sólo se usa en Firefox
*********************************************************************************************/
function FRMFocoNoSelect(Nombre)
{
var Ele,a;

	i=document.getElementsByName(Nombre);
	if (i.length==0) return;
	Ele=i[0];
	a=Ele.value;

	if (a.length>1)
		{
		Ele.value=a.substr(0,a.length-1);
		Ele.value+=a.substr(a.length-1);
		}
	else
		{
		Ele.value='';
		Ele.value=a;
		};
};
	

/***************************************************************************************
 Se le pasa una referencia al elemento de un formulario que tiene el foco.
Mueve el foco al siguiente elemento del formulario que pueda recibirlo
 Atiende primero al tabindex, después a la posicion dentro del formulario
***************************************************************************************/
function FRMFocoSig(Ele)
{
var i,idx=-1;
var ti;

	//Ordenamos una matriz con todos los elementos del form en base a su tabIndex y posicion en el form
	ti=FRMOrdenaTabIndex(Ele.form);
	
	//Buscamos el indice de nuestro elemento dentro de la matriz
	for (i=0;ti.length>i;i++)
		if (ti[i]==Ele)
			{
			idx=i;
			break;	//Forzamos la salida
			};
			
	if (idx!=-1)
		{
		idx++;	//Siguiente elemento
		if (idx==ti.length) idx=0;		//Si el siguiente era el último, volvemos al primero
		if (ti[idx]==Ele) return -1;	//Si hemos vuelto a nuestro elemento, es que nadie salvo él puede recibir el foco
		ti[idx].focus();				//Le damos el foco
		};
};

/*************************************************************************************
 Se le pasa una referencia al elemento de un formulario que tiene el foco.
 Mueve el foco al elemento anterior del formulario que pueda recibirlo
 Atiende primero al tabindex, despues a la posicion dentro del formulario
*************************************************************************************/
function FRMFocoAnt(Ele)
{
var i,idx=-1;
var ti;

	//Ordenamos una matriz con todos los elementos del form en base a su tabIndex y posicion en el form
	ti=FRMOrdenaTabIndex(Ele.form);
	
	//Buscamos el indice de nuestro elemento dentro de la matriz
	for (i=0;ti.length>i;i++)
		if (ti[i]==Ele)
			{
			idx=i;
			break;	//Forzamos la salida
			};
			
	if (idx!=-1)
		{
		idx--;	//Siguiente elemento
		if (idx==-1) idx=ti.length-1;	//Si el siguiente era el último, volvemos al primero
		if (ti[idx]==Ele) return -1;	//Si hemos vuelto a nuestro elemento, es que nadie salvo él puede recibir el foco
		ti[idx].focus();				//Le damos el foco 
		};
};



/**************************************************************************
 Devuelve una matriz con todos los elementos del formulario que se le pasa 
 ordenados de mayor a menor respecto a su tabIndex, y sean receptivos de
 tener el foco
**************************************************************************/
function FRMOrdenaTabIndex(Frm)
{
var FrmEle=Frm.elements;	//Referenciamos a los elementos del form
var tindex=new Array();		//Me creo una matriz donde meteré cada elemento del form
var flag,c;

	//Copiamos las referencias de los elementos del form a la matriz
	//Solo de aquellos elementos suceptibles de recibir el foco
	for (i=0;FrmEle.length>i;i++)
		{
		flag=true;
		if (FrmEle[i].style.visibility=='hidden') flag=false;
		if (FrmEle[i].disabled) flag=false;
		if (FrmEle[i].type=='hidden' || !FrmEle[i].type) flag=false;
		if (flag) tindex[tindex.length]=FrmEle[i];
		};
	
	//Lo ordenamos. OJO no podemos usar el sort del array, por que ordena seguramente con un metodo dicotomico, y nos desordena
	//las posiciones de los tabindex que son iguales, y esas debemos respetarlas.
	do
	{
		flag=false;
		for (i=0;i<tindex.length-1;i++)
			{
			if (FRMOrdenaTabIndexCompara(tindex[i],tindex[i+1]))
				{
				c=tindex[i];
				tindex[i]=tindex[i+1];
				tindex[i+1]=c;
				flag=true;
				};
			};
	}
	while (flag);
	

	// y devolvemos la matriz
	return tindex;
};
//Funcion para el sort del array
//Si devuelve false los deja igual
//Si devuelve true "a" mas abajo en la tabla 
function FRMOrdenaTabIndexCompara(a,b)
{
var tiA=parseInt(a.tabIndex,10);
var tiB=parseInt(b.tabIndex,10);

	if (tiA<tiB) 
		return true;
	else
		return false;
};


/*********************************************************************************
 Le damos el foco al primer campo del primer formulario que haya en el documento
**********************************************************************************/
function FRMPonFoco()
{
var i,j;
var ti;

	//Bucle para todos los formularios
	for (j=0;j<document.forms.length;j++)
		{
		//Ordenamos una matriz con todos los elementos del form en base a su tabIndex 
		//y posicion en el form que puedan tener el foco
		ti=FRMOrdenaTabIndex(document.forms[j]);
		if (ti.length>0) 
			{
			ti[0].focus();
			return;
			};
		};
};


/**********************************************************************************
  Pone el foco al último elemento boton (INPUT type=button) asociado a una 
  clase FRMBoton que lo tuvo. Si este no puede pone el foco en el primer elemento 
  boton (INPUT type=button) asociado a una clase FRMBoton que pueda tenerlo
***********************************************************************************/
function FRMFocusFirstBtn()
{
var i;
var ob;

	ob=FRMLastFocusBtn();	//El último que lo tuvo
	if (ob!=null)
		if (!ob.disabled && ob.style.visibility!='hidden')
			{
			//Puede tenerlo
			ob.focus();
			return;
			};

	//El ultimo no podía tenerlo, me recorro lo matriz de botones
	for (i=0;i<FRMBtns.length;i++)
		if (!FRMBtns[i].obj.disabled && FRMBtns[i].obj.style.visibility!='hidden')
			{
			FRMBtns[i].obj.focus();
			break;
			};
};


/*********************************************************************
Devuelve el elemento boton (INPUT type=button) asociado a una 
clase FRMBoton que tuvo el foco por última vez. (Aún puede tenerlo). 
Si devuelve null, es que ningún botón lo tuvo nunca.
*********************************************************************/
function FRMLastFocusBtn()
{
var o=null;
var i;

	for (i=0;i<FRMBtns.length;i++)
		if (FRMBtns[i].lastFocus)
			{
			o=FRMBtns[i].obj;
			break;
			};
			
	return o;
};

/********************************************************************
 Conecta el control de la pulsación de las teclas de acceso directo 
 a los botones de la clase FRMBoton
********************************************************************/
function FRMIniKeyAccessBtn()
{
	//Asignamos la función de control de las teclas para botones
	EVTAsignaEvento(document.body, 'keydown', FRMVerificaClickBtn); 
	EVTAsignaEvento(document.body, 'keyup', FRMVerificaClickBtn);
};

/********************************************************************
 Desconecta el control de la pulsación de las teclas de acceso directo 
 a los botones de la clase FRMBoton
********************************************************************/
function FRMEndKeyAccessBtn()
{
	//Eliminamos la función de control de las teclas para botones
	EVTEliminaEvento(document.body, 'keydown', FRMVerificaClickBtn); 
	EVTEliminaEvento(document.body, 'keyup', FRMVerificaClickBtn);
};

//A esta funcion la llaman el keyup y keydown del body, para controlar los botones
function FRMVerificaClickBtn(evt)
{
var i;

	evt=EVTDimeEvento(evt);	//Cogemos la referencia valida al evento dependiendo del navegador
		
	//Nos buclamos para todos los botones definidos 
	for (i=0;i<FRMBtns.length;i++)
		{
		//Sólo comprobamos si tiene teclas de acceso, esta visible y no esta disabled
		if (FRMBtns[i].keyAccess!=null && !FRMBtns[i].obj.disabled && FRMBtns[i].obj.style!='hidden')
			if ( FRMVerificaClick(FRMBtns[i].keyAccess,evt) )
				{
				//Este boton tiene las teclas pulsadas. En el keydown le hacemos el click
				if (evt.type=='keydown') FRMBtns[i].click(); 
				EVTNoPropagarEvento(evt); //Y el evento no pasa, que nos valia
				break;	//Y no miramos más
				};
		};
};


/************************************************************************************
 Devuelve true si algún campo asociado a la clase FRMCampo cambio de contenido.
 False si no hay cambios
************************************************************************************/
function FRMControlChangeCmp()
{
var j;

	for (j=0;j<FRMCmps.length;j++)
		if (FRMCmps[j].statusChange()) return true;
	return false;
};


/*************************************************************************************************************
 Se le pasa una referencia a un document (normalmente leido en un IFRAME oculto que traiga la lectura valida
 de un registro segun la estrucura de ficheros SQL ASP y una referencia a un formulario.
 Rellena los campos del formulario e inicializa las referencias a CMPCampo que tengan los campos
*************************************************************************************************************/
function FRMPaintData(ifrDOC,frm)
{
var i,j,k,cmp,FRC;
var frmCmp=frm.elements; //Elementos del formulario

	//Buscamos el registro
	cmp=ifrDOC.getElementById('registro').getElementsByTagName('SPAN'); //Buscamos los campos del registro en el documento

	//Nos buclamos por cada campo que nos devuelve el registro
	for(i=0;i<cmp.length;i++)
		{
		//Ahora nos buclamos para buscar el nombre del campo en el formulario
		for (j=0;j<frmCmp.length;j++)
			{
			t=frmCmp[j].name;	//Cojo el nombre del campo del formulario
			k=t.indexOf('_c');	//Busco 
			if (k!=0)
				{
				t=t.substr(0,k);	//Cojo sólo el nombre para comparar con el que me viene en el IFRAME
				if (cmp[i].id==t)
					{
					//Aqui hay que distinguir si es un select texarea o input
					switch (frmCmp[j].tagName)
						{
						case 'TEXTAREA':
						case 'INPUT':
							frmCmp[j].value=cmp[i].innerHTML;
							break;
						
						case 'SELECT':
							COMBOSelValue(frmCmp[j],cmp[i].innerHTML);
							break;
						};
					
					//Ya no hay cambios en este campo (miramos si tiene clase FRMCampo asociada
					FRC=FRMCmps[frmCmp[j].name];
					if (FRC)
						{
						FRC.statusChangeReset();
						//Y si tiene una busqueda asociada, la ponemos que los datos son correctos
						if (FRC.relationData!=null)
							FRMCmpsREL[FRC.relationData].statusReset(true);
						};
					};
				};
			};
		};
};




/************************************************************************************************
 Crea los campos dentro del formulario para mantener las claves del registro actual
 Se le pasa una referencia a un formulario y crea un input hidden con nombre y la id xxx_regact
 por cada campo cuyo nombre sea xxx_clv
*************************************************************************************************/
function FRMCreateRegAct(frm)
{
var frmCmp=frm.elements; //Elementos del formulario
var t,j,i,ne;

	//Me busco dentro del formulario todos los campos
	for (j=0;j<frmCmp.length;j++)
		{
		t=frmCmp[j].name;
		i=t.indexOf('_clv');
		if (i!=-1)	
			{
			ne=document.createElement('INPUT');
			ne.type='hidden';
			ne.value='';
			ne.name=t.slice(0, i) + '_regact';
			frm.appendChild(ne); //Y se lo añadimos al formulario nuevo
			};
		};
};


/****************************************************************************
Se le pasa un nombre de un campo, y opcionalmente un objeto document 
(si no se le pasa, cogerá la referencia document)
y busca el nombre del campo en todos los formularios del documento 
si existe devuelve su valor
si no existe, null
****************************************************************************/
function FRMCmpGetValue(name,doc)
{
var j,i;
var frm;

	if (typeof(doc)=='undefined')
		frm=document.forms;
	else
		frm=doc.forms;

	for(i=0;i<frm.length;i++)
		for (j=0;j<frm[i].elements.length;j++)
			if (frm[i].elements[j].name==name)	
				return frm[i].elements[j].value;
	return null;
};

/***************************************************************************************************
Se le pasa un nombre de un campo y un valor, y opcionalmente un objeto document 
(si no se le pasa, cogerá la referencia document)
y busca el nombre del campo en todos los formularios del documento 
si existe actualiza su valor y devuelve true
si no existe devuelve false
***************************************************************************************************/
function FRMCmpPutValue(name,value,doc)
{
var j,i;
var frm;

	if (typeof(doc)=='undefined')
		frm=document.forms;
	else
		frm=doc.forms;

	for(i=0;i<frm.length;i++)
		for (j=0;j<frm[i].elements.length;j++)
			if (frm[i].elements[j].name==name)
				{
				frm[i].elements[j].value=value;
				return true;
				};
	return false;
};

/**************************************************************************************************
 Copia los valores actuales de las claves a los campos ocultos de clave actual
 Se le pasa la referencia a un formulario y busca todos los campos que cuyo
 nombre sea xxx_regact y copia en ellos el valor del campo de mismo nombre que acabe en xxx_clv
***************************************************************************************************/
function FRMClvToRegAct(frm)
{
var frmCmp=frm.elements; //Elementos del formulario
var t,j,i,ne,n;

	//Me busco dentro del formulario todos los campos
	for (j=0;j<frmCmp.length;j++)
		{
		t=frmCmp[j].name;
		i=t.indexOf('_regact');
		if (i!=-1)	
			{
			n=t.slice(0, i) + '_clv'; //Si existe el regact tiene que existir el clv, ya que se crea a partir de él
			ne=frmCmp[n];
			frmCmp[j].value=ne.value;
			};
		};
		
};

/***********************************************************************************************
 Se le pasa una referencia a un formulario e inicializa todos los campos
 atendiendo primero a la clase CMPCampo para el valor inicial, y si no tiene asiganda
 clase CMPCampo poniendolo a pelo
************************************************************************************************/
function FRMInitCmpForm(frm)
{
var j,FRC,t;
var frmCmp=frm.elements;

	//Inicializa todos los datos
	for (j=0;j<frmCmp.length;j++)
		{
		FRC=FRMCmps[frmCmp[j].name];	//Me busco a ver si tiene referencia a la clase de campos
		if (FRC)
			{
			//La tiene, asi que llamo a su funcion de inicializar
			FRC.init();
			FRC.statusChangeReset();
			//Cambiamos el status de los datos relacionados		
			if (FRC.relationData!=null) FRMCmpsREL[FRC.relationData].statusReset(false);
			}
		else
			{
			//Un campo del formulario que no tiene asignada clase de campo (puede ser de los de apoyo)
			t=frmCmp[j].name;
			if (t.indexOf('_c')!=-1)
				{
				switch(frmCmp[j].tagName)
					{
					case 'TEXTAREA':
					case 'INPUT':
						frmCmp[j].value='';
						break;
						
					case 'SELETC':
						frmCmp[j].selectedIndex=0;
						break;
					};
				};
			};
		};
};


/***********************************************************************************************
 Inicializa todos los campos cuyo nombre sea xxx_clv siempre que tengan asignada clase CMPCampo
************************************************************************************************/
function FRMInitClv()
{
var j;

	//Me busco todas las instancias de FRMCampo
	for (j=0;j<FRMCmps.length;j++)
		if (FRMCmps[j].obj.name.indexOf('_clv')!=-1)
			{
			//Es una clave
			FRMCmps[j].init();
			FRMCmps[j].statusChangeReset();
			//Cambiamos el status de los datos relacionados		
			if (FRMCmps[j].relationData!=null) FRMCmpsREL[FRMCmps[j].relationData].statusReset(false);
			};
};

/*************************************************************************************************************
 Busca todas las instancias de la clase FRMCampo cuyo elemento asociado tenga por nombre
 xxx_clv, xxx_cmp o xxx_crl y le cambia al elemento la propiedad disabled
 Si se le pasa true, es que estan activos (enabled) y si se le pasa false, es que estan inactivos (disabled)
*************************************************************************************************************/
function FRMStatusCmp(flag)
{
var j,i,ext;

	//Me busco todas las instancias de FRMCampo
	for (j=0;j<FRMCmps.length;j++)
		{
		//Desgloso la extension
		t=FRMCmps[j].obj.name;
		i=t.indexOf('_');
		if (i!=-1) 
			{
			ext=t.substr(i+1);
			if (ext=='clv' || ext=='cmp' || ext=='crl')
				FRMCmps[j].enabled(flag);
			};
		};
};


/*************************************************************************************************************
 Busca todas las instancias de la clase FRMBoton y le cambia al elemento asociado la propiedad disabled
 Si se le pasa true, es que estan activos (enabled) y si se le pasa false, es que estan inactivos (disabled)
 Si ademas se le pasa el parametro group, lo hará sólo para las instancias de la clase cuya propiedad group coincide
 con la pasada. Si no se le pasa el parametro group, lo hara para todas.
*************************************************************************************************************/
function FRMStatusBtn(flag, group)
{
var j;

	//Me busco todas las instancias de FRMBoton
	for (j=0;j<FRMBtns.length;j++)
		if ( (typeof group!='undefined' && FRMBtns[j].group==group) || typeof group=='undefined')
			FRMBtns[j].enabled(flag);
};


/*****************************************************************************************************
Busca todas las instancias de la clase FRMBoton y actualiza la propiedad blocked de cada una con 
el flag que se le pasa (true o false). Si ademas se le pasa el parametro group, lo hará sólo para
las instancias de la clase cuya propiedad group coincide con la pasada. Si no se le pasa el parametro
group, lo hara para todas.
*****************************************************************************************************/
function FRMBlockedBtn(flag, group)
{
	//Me busco todas las instancias de FRMBoton
	for (j=0;j<FRMBtns.length;j++)
		if ( (typeof group!='undefined' && FRMBtns[j].group==group) || typeof group=='undefined')
			FRMBtns[j].blocked=flag;
};


/***************************************************************************************************************************
 Todos los parametros son cadena
 win		Una referencia a una ventana (p.ej. miwin=new WINClassVentana, se le pasa 'miwin') Utiliza eval para hacerla efectiva
 ejecuta	El nombre de una funcion que se ejecutará cuando cambie el valor de los campos (p.ej. 'funcion("p");'
 Luego recibe x parametros más, siempre pares, en el que van "id de un campo","valor"
 
 Inicia el checkeo del cambio de valor de esos campos, mientras la ventana este abierta. 
 En el momento que cambie el valor del campo respecto al paado cierra la ventana y ejecuta la funcion que se le había pasado
****************************************************************************************************************************/
function FRMCheckSearchWin(win,ejecuta)
{
var cmp=new Array();
var j,i;
var campos='',llamada;
var RefWin,obj;

	RefWin=eval(win);	//Saco una referencia válida a la ventana
	if (RefWin.EstaAbierta())
		{
		//Saco los campos y me preparo una cadena para el setTimeout
		for (j=2; FRMCheckSearchWin.arguments.length>j; j+=2)
			{
			i=cmp.length;
			cmp[i]=new Array();
			cmp[i][0]=FRMCheckSearchWin.arguments[j];	//Nombre del campo
			cmp[i][1]=FRMCheckSearchWin.arguments[j+1];	//Valor
			
			//Preparo cadena
			campos+=',\"' + cmp[i][0] + '\",\"' + cmp[i][1] + '\"'; // es decir ,"micampo","mivalor" ...
			};
		 
		//Y ahora los voy buscando
		for (j=0;j<cmp.length;j++)
			{
			if (FRMCmpGetValue(cmp[j][0])!=cmp[j][1])
				{
				//Ha cambiado, hago mis movidas
				RefWin.Cierra();	//Y la cierro
				if (ejecuta!='') eval(ejecuta);		//Y ejecuto la funcion de marras
				return;
				};
			};

		//No han cambiado, me preparo para autollamarme
		llamada='FRMCheckSearchWin(\'' + win + '\',\'' + ejecuta + '\'' + campos + ')';
		setTimeout(llamada,100);
		};
};

/***********************************************************************************************
 Pasa los datos leidos en el IFRAME a la tabla, basandose en el 
 numero de filas de la tabla.
 En el IFRAME se ha leido una estructura del tipo Pagina
 Las cabeceras de la tabla tienen los <TD> cada uno con la Id del campo que va en esa columna
***********************************************************************************************/
function FRMPaintDataTable(doc,tabla)
{
var celdas=tabla.rows[0].cells;	//celdas de la cabecera
var nr,nc,valor;

	//Bucle para los x registros por pagina
	for (nr=1; nr <= tabla.rows.length-1; nr++)
		{
		//Bucle para cada celda de la cabecera (que nos dara el nombre del campo)
		for (nc=0;nc<celdas.length;nc++)
			{
			campo=celdas[nc].id + '-' + nr;				//Nombre de la Id a buscar
			valor=doc.getElementById(campo).innerHTML;	//Sacamos el valor del documento del IFRAME
			if (valor=='') valor='&nbsp;';				//Si esta vacio, un blanco
			tabla.rows[nr].cells[nc].innerHTML=valor;	//Y se lo metemos a la celda
			};
		};
};

/**************************************************************
 Borra los datos de la tabla basandose en el numero de filas.
***************************************************************/
function FRMEmptyDataTable(tabla)
{
var celdas=tabla.rows[0].cells;	//celdas de la cabecera
var nr,nc;

	//Bucle para los x registros por pagina
	for (nr=1; nr <= tabla.rows.length-1; nr++)
		{
		//Bucle para cada celda de la cabecera (que nos dara el nombre del campo)
		for (nc=0;nc<celdas.length;nc++)
			tabla.rows[nr].cells[nc].innerHTML='&nbsp;';
		};
};

/***************************************************************************
 Le crea las filas y columnas necesarias a la tabla de busqueda, 
 a partir de las cabeceras. Conecta los eventos de doble click, mouse over 
 y mouse out a la función que se le pasa como parametro
***************************************************************************/
function FRMCreateRowsSearch(objTable, NRows, myFunction)
{
var NCols;
var i,j,r,c;

	//Numero de columnas, que cojo de las cabeceras
	NCols=objTable.rows[0].cells.length;

	//El cuerpo de la tabla
	tbody=objTable.tBodies[0];

	//Bucle para añadir filas / columnas	
	for (i=0;i<NRows;i++)
		{
		//Creo la fila
		r=tbody.insertRow(-1);
		
		//Asignarle los eventos (lo va a disparar el TD, no el TR)
		EVTAsignaEvento(r, 'dblclick', EventoFilas);
		EVTAsignaEvento(r, 'mouseover', EventoFilas);
		EVTAsignaEvento(r, 'mouseout', EventoFilas);
		
		//Creo las columnas
		for (j=0;j<NCols;j++)
			{
			c=r.insertCell(j);
			c.align=objTable.rows[0].cells[j].align; //Le ponemos la misma alineacion que las cabeceras
			}
		};
};


/*****************************************************************************
	Busca todos los campos que tengan la extensión _linclv y monta una
	cadena de parametros con sus valores para pasar mediante el querystring
	Opcionalmente se le puede pasar un documento concreto, si no se le pasa
	busca en document
	Si no encuentra ninguno devuelve cadena vacia
*****************************************************************************/
function FRMLinClvToQueryString(doc)
{
var c='';
var j,i;
var frm;

	if (typeof(doc)=='undefined')
		frm=document.forms;
	else
		frm=doc.forms;

	for(i=0;i<frm.length;i++)
		for (j=0;j<frm[i].elements.length;j++)
			if (frm[i].elements[j].name.indexOf('_linclv')!=-1)
				c+='&' + frm[i].elements[j].name + '=' + escape(frm[i].elements[j].value);
	return c;
};

/**************************************************************************************************
 Busca en el QueryString todos los valores xxx_linclv y crea una nueva cadena query sólo con ellos
**************************************************************************************************/
function FRMQueryLinClvToQuery()
{
var q=OBJQueryStringToArray();
var ret='';
var i;

	for (i=0;i<q.length;i++)
		if (q[i].name.indexOf('_linclv')!=-1)
			ret+='&' + q[i].name + '=' + escape(q[i].value);
	return ret;
};

/********************************************************************************************
 Mira todos los parametros recibidos en el query si tienen correspondencia con algun campo
 del documento. Si es asi, al campo le asigna el valor que viniera en el querystring
 Opcionalmente se le puede pasar un documento concreto, si no se le pasa busca en document
********************************************************************************************/
function FRMQueryStringToCmp(doc)
{
var Q=OBJQueryStringToArray(); //Montamos una matriz con lo recibido en el query
var j,i,k;
var frm,cmp;
var ret=false;

	if (typeof(doc)=='undefined')
		frm=document.forms;
	else
		frm=doc.forms;

	//Me buclo buscando en cada parametro recibido en el query
	for(k=0;k<Q.length;k++)
		{
		//Ahora busco para ese parametro un nombre de campo en el/los formularios
		for(i=0;i<frm.length;i++)
			for (j=0;j<frm[i].elements.length;j++)
				if (frm[i].elements[j].name==Q[k].name)
					{
					//Este campo coincide con un parametro
					//Depende del tipo de campo que sea
					cmp=frm[i].elements[j];
					switch (cmp.tagName)
						{
						case 'INPUT':
						case 'TEXTAREA':
							cmp.value=Q[k].value;
							ret=true;
							break;
						
						case 'SELECT':
							COMBOSelValue(cmp,Q[k].value);
							ret=true;
							break;
						};
					};
		};
		
	return ret;
};

/********************************************************************************
 Se le pasa una referencia a un formulario y busca los campos _regact.
 Comprueba si value!=''. 
 Si hay datos en esos campos devuelve false. Si no hay datos devuelve true
********************************************************************************/
function FRMRegActIsEmpty(frm)
{
var j;

	for (j=0;j<frm.elements.length;j++)
		if (frm.elements[j].name.indexOf('_regact')!=-1)
			if (frm.elements[j].value!='') return false;
	return true;
};


/****************************************************************************************
 Crea el parametro VolverA, asignandole la url actual y añadiendole como parametros
 los campos y sus valores actuales que se le pasen como parametros.
 Si en la url actual hay un parametro VolverA, lo extrae y lo coloca el último
*****************************************************************************************/
function FRMUrlVolverA()
{
var i,campo;
var url=window.location.pathname;	//la url sin parametros (/procesos/miproceso.asp)
var car='?';
var parametros;
var cmp=new Array();

	//Todos los campos que nos pasen, se los pongo como parámetros
	for (i=0;i<FRMUrlVolverA.arguments.length;i++)
		{
			campo=FRMUrlVolverA.arguments[i];	//Nombre del campo que me pasan
			url+=car + campo + '=' + FRMCmpGetValue(campo);	//o ?NombreCampo=ValorCampo o &NombreCampo=ValorCampo
			car='&';	//A partir del primero, ya siempre &
			
			cmp[campo]=true;	//Me creo un elemento en la matriz con el nombre del campo
		};
		
		
	//Y ahora añadimos el resto de parametros que tenía la direccion, salvo si coincide con algun campo 
	//que nos hayan pasado
	parametros=OBJQueryStringToArray();	//Cojo los parametros actuales en una matriz
	for (i=0;i<parametros.length;i++)
		{
		//Si es VolverA, no, le dejamos para el final. Si es el mismo campo que nos han pasado lo ignoro
		if (parametros[i].name!='VolverA' && !cmp[parametros[i].name])
			{
			url+=car + parametros[i].name + '=' + parametros[i].value;
			car='&';	//A partir del primero, ya siempre &
			};
		};
		
	//Y si tiene el volver, pos se lo añado
	if (parametros['VolverA']) url+=car + 'VolverA=' + parametros['VolverA'].value;
	
	//Y devuelvo el tema	
	return 'VolverA=' + escape(url);
};


/*************************************************************************************
 Crea una cadena del tipo Campo=Valor&Campo=Valor cogiendo los parametros que recibe.
 Los paramtros tienen la estructura:
 'campo_del_documento:nombre_a_pasar'
 La cadena que monta es:
 nombre_a_pasar=escape(campo_del_documento.value)
*************************************************************************************/
function FRMUrlCampos()
{
var i,campos;
var ret='';

	//Todos los campos que nos pasen, se los pongo como parámetros
	for (i=0;i<FRMUrlCampos.arguments.length;i++)
		{
			campo=FRMUrlCampos.arguments[i].split(':');	//Separo nombre del formulario y el nombre a pasar
			if (ret!='') ret+='&';
			ret+=campo[1] + '=' + escape(FRMCmpGetValue(campo[0]));
		};
		
	return ret;
};

/******************************************************************************************************************
 Se le pasa una cadena con una definición de teclas
 y el evento de keydown (importante, el keypress no vale) y verifica si la tecla pulsada corresponde 
 con alguna de las definiciones
 Las definiciones de teclas son cadenas separadas por comas. Las teclas ctrl shift y alt se asocian con un '+'
 Ctr  Ej.
 'Derecha,Alt+Ctrl+F2' 'Tecla cursor derecha o alt ctrl y f2 a la vez
 'A'	' Tecla A
 
 Los nombres de las teclas estan definidos en la funcion FRMDimeCodTecla
 
 Si alguna de las definiciones coincide con lo pulsado devuelve true, si no, false
******************************************************************************************************************/
function FRMVerificaClick(DefTeclas,evt)
{
var GruposTeclas=DefTeclas.split(',');	//Nos creamos una matriz con las definiciones individuales
var Teclas;
var i;
var tec,esp;
var PulsadoEsp=0;
var PulsadoTec=0;

	//Nos preparamos una variable con la convinacion de teclas especiales pulsadas
	if (evt.shiftKey) PulsadoEsp|=1;
	if (evt.ctrlKey) PulsadoEsp|=2;
	if (evt.altKey) PulsadoEsp|=4;

	PulsadoTec=EVTDimeTecla(evt,'CODE');

	//Vamos mirando cada definicion
	for (i=0;i<GruposTeclas.length;i++)
		{
		//Desglosamos la secuencia de teclas
		Teclas=GruposTeclas[i].split('+');
		esp=0;
		tec=-1;
		for (j=0;j<Teclas.length;j++)
			{
			switch (Teclas[j].toUpperCase())
				{
				case 'SHIFT':
					esp|=1;
					break;

				case 'CTRL':
					esp|=2;
					break;
				
				case 'ALT':
					esp|=4;
					break;
				
				default:
					tec=FRMDimeCodTecla(Teclas[j]);
					break;
				};
			};
		//Ahora comparamos
		if (PulsadoTec==tec && PulsadoEsp==esp)
			return true;
		};

	return false;
};

/**********************************************************************
 Se le pasa un nombre de una tecla y devuelve el código de la misma
 Si no la tiene en la lista, devuelve -1
**********************************************************************/
function FRMDimeCodTecla(txt)
{
var cod=-1;
    
 	switch (txt.toUpperCase())
		{
        case 'ESC':
            cod = 27;
			break;
        case 'BACKSPACE':
            cod = 8;
			break;
        case 'ENTER':
            cod = 13;
			break;
        case 'F1':
            cod = 112;
			break;
        case 'F2':
            cod = 113;
			break;
        case 'F3':
            cod = 114;
			break;
        case 'F4':
            cod = 115;
			break;
        case 'F5':
            cod = 116;
			break;
        case 'F6':
            cod = 117;
			break;
        case 'F7':
            cod = 118;
			break;
        case 'F8':
            cod = 119;
			break;
        case 'F9':
            cod = 120;
			break;
        case 'F10':
            cod = 121;
			break;
        case 'F11':
            cod = 122;
			break;
        case 'F12':
            cod = 123;
			break;
        case '0':
            cod = 48;
			break;
        case '1':
            cod = 49;
			break;
        case '2':
            cod = 50;
			break;
        case '3':
            cod = 51;
			break;
        case '4':
            cod = 52;
			break;
        case '5':
            cod = 53;
			break;
        case '6':
            cod = 54;
			break;
        case '7':
            cod = 55;
			break;
        case '8':
            cod = 56;
			break;
        case '9':
            cod = 57;
			break;
        case 'ESPACIO':
            cod = 32;
			break;
        case 'A':
            cod = 65;
			break;
        case 'B':
            cod = 66;
			break;
        case 'C':
            cod = 67;
			break;
        case 'D':
            cod = 68;
			break;
        case 'E':
            cod = 69;
			break;
        case 'F':
            cod = 70;
			break;
        case 'G':
            cod = 71;
			break;
        case 'H':
            cod = 72;
			break;
        case 'I':
            cod = 73;
			break;
        case 'J':
            cod = 74;
			break;
        case 'K':
            cod = 75;
			break;
        case 'L':
            cod = 76;
			break;
        case 'M':
            cod = 77;
			break;
        case 'N':
            cod = 78;
			break;
        case 'O':
            cod = 79;
			break;
        case 'P':
            cod = 80;
			break;
        case 'Q':
            cod = 81;
			break;
        case 'R':
            cod = 82;
			break;
        case 'S':
            cod = 83;
			break;
        case 'T':
            cod = 84;
			break;
        case 'U':
            cod = 85;
			break;
        case 'V':
            cod = 86;
			break;
        case 'W':
            cod = 87;
			break;
        case 'X':
            cod = 88;
			break;
        case 'Y':
            cod = 89;
			break;
        case 'Z':
            cod = 90;
			break;
        case '?':
            cod = 219;
			break;
        case '¿':
            cod = 221;
			break;
        case '\\':
            cod = 220;
			break;
        case '[':
            cod = 186;
			break;
        case ']':
            cod = 187;
			break;
        case 'ENE':
            cod = 192;
			break;
        case '{':
            cod = 222;
			break;
        case 'COMA':
            cod = 188;
			break;
        case '.':
            cod = 190;
			break;
        case '-':
            cod = 189;
			break;
        case 'ARRIBA':
            cod = 38;
			break;
        case'ABAJO':
            cod = 40;
			break;
        case'IZQUIERDA':
            cod = 37;
			break;
        case'DERECHA':
            cod = 39;
			break;
        case'INSERT':
            cod = 45;
			break;
        case'SUPR':
            cod = 46;
			break;
        case'INICIO':
            cod = 36;
			break;
        case'FIN':
            cod = 35;
			break;
        case'REPAG':
            cod = 33;
			break;
        case'AVPAG':
            cod = 34;
			break;
        case'NUM_0':
            cod = 96;
			break;
        case'NUM_1':
            cod = 97;
			break;
        case'NUM_2':
            cod = 98;
			break;
        case'NUM_3':
            cod = 99;
			break;
        case'NUM_4':
            cod = 100;
			break;
        case'NUM_5':
            cod = 101;
			break;
        case'NUM_6':
            cod = 102;
			break;
        case'NUM_7':
            cod = 103;
			break;
        case'NUM_8':
            cod = 104;
			break;
        case'NUM_9':
            cod = 105;
			break;
        case'NUM_/':
            cod = 111;
			break;
        case'NUM_*':
            cod = 106;
			break;
        case'NUM_MENOS':
            cod = 109;
			break;
        case'NUM_MAS':
            cod = 107;
			break;
        case'NUM_SUPR':
            cod = 110;
			break;
        case'BLOQNUM':
            cod = 144;
			break;
        case'PAUSA':
            cod = 19;
			break;
        case'BLOQDESP':
            cod = 145;
			break;
		};
    return cod;
};


/****************************************************************************
 Comprueba que un valor sea una numero.
 numEnteros el número máximo de enteros
 numDecimales el numero máximo de decimales
 signo (true o false) si permite o no el signo menos
 Devuelve true si correcto, false si no
****************************************************************************/ 
function FRMChekNumber(valor,numEnteros,numDecimales,signo)
{
var c;
var de='',si='';
var RE;

	//OJO. Se usa \\d por tener que asignar los valores a una cadena, y no directamente a la expresion regular
	if (numDecimales>0)	de='(,\\d{1,' + numDecimales + '})?';
	if (signo) si='-?';
	c='^' + si + '\\d{1,' + numEnteros +'}' + de + '$'; //Nos montamos la expresion regular
	RE=new RegExp(c,'g');

    return RE.test(valor);
};


/****************************************************************************
 Comprueba que un valor sea una fecha. Devuelve true si correcto, false si no
****************************************************************************/ 
function FRMChekDate(value)
{
	var er_mes31dias = /^([1-3]0|[0-2][1-9]|31|[0-9])\/(1|01|3|03|5|05|7|07|8|08|10|12)\/(1999|20[0-1][0-9]|2020)$/;
	var er_mes30dias = /^([1-3]0|[0-2][1-9]|[0-9])\/(4|04|6|06|9|09|11)\/(1999|20[0-1][0-9]|2020)$/;
	var er_mes28dias = /^([1-2]0|[0-2][1-8]|[0-1]9|[0-9])\/(02|2)\/(1999|200[1-3]|200[5-7]|2009|201[0-1]|201[3-5]|201[7-9])$/;
	var er_mes29dias = /^([1-2]0|[0-2][1-9]|[0-9])\/(02|2)\/(2000|2004|2008|2012|2016|2020)$/;

	//comprueba la fecha segun calendario (hasta el 2020, ojo)
	if (!(er_mes31dias.test(value) || er_mes30dias.test(value) || er_mes29dias.test(value) || er_mes28dias.test(value)))
		return false;
	else
		return true;
};


/*************************************************************************************
 Comprueba que un valor sea una hora. True si OK, false si no
*************************************************************************************/
function FRMCheckHour(value)
{
var RE=/^(0[0-9]|1\d|2[0-3]):([0-5]\d):([0-5]\d)$/;

    return RE.test(value);
};

/*************************************************************************************
 Comprueba que un valor sea una direccion de mail coherente. True si OK, false si no
*************************************************************************************/
function FRMCheckMail(value)
{
var RE=/[\w-\.]{3,}@([\w-]{2,}\.)*([\w-]{2,}\.)[\w-]{2,4}/;

    return RE.test(value);
};

/*************************************************************************************
 Comprueba que un valor sea una url coherente. True si OK, false si no
*************************************************************************************/
function FRMCheckWeb(value)
{
var RE=/^(ht|f)tp(s?)\:\/\/[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*(:(0-9)*)*(\/?)( [a-zA-Z0-9\-\.\?\,\’\/\\\+&amp;%\$#_]*)?$/;

    return RE.test(value);
};

/*************************************************************************************
 Comprueba que un valor sea un valor hexadecimal. True si OK, false si no
*************************************************************************************/
function FRMCheckHexa(value)
{
var RE=/^([a-f]|[A-F]|\d)+$/;

    return RE.test(value);
};

/*************************************************************************
	Comprueba que en valor no haya ningún caracter de 'cadena'
	devuelve true si correcto, false si hay alguno
*************************************************************************/
function FRMCheckCharInvalid(valor,cadena)
{
	for (var i=0;valor.length>i;i++)
		if (cadena.indexOf(valor.substr(i,1))!=-1) return false;
	return true;
};

/*************************************************************************
	Comprueba que en valor solo haya caracteres contenidos en 'cadena'
	devuelve true si es correcto, false si alguno no esta
*************************************************************************/
function FRMCheckCharOnly(valor,cadena)
{
	for (var i=0;valor.length>i;i++)
		if (cadena.indexOf(valor.substr(i,1))==-1) return false;
	return true;
};

/***********************************************************************************************
 Comprueba que un valor contenga sólo signos alfabéticos, sin espacios. True si OK, false si no
***********************************************************************************************/
function FRMCheckAlphabetical(value)
{
var RE=/^([a-z]|[A-Z])+$/;

	return RE.test(value);
};


/***********************************************************************************************
 Comprueba que un valor contenga un CIF valido. True si OK, false si no
 (tipo LDDDDDDDC	letra - 7 digitos - letra o digito de control)
***********************************************************************************************/
function FRMCheckCIF(value)
{
var v1 = new Array(0,2,4,6,8,1,3,5,7,9); 
var temp = 0;
var tempL;
var dc;

	if (!/^[A-Za-z0-9]{9}$/.test(value))  // Son 9 dígitos?
		return false;
	else 
		if (!/^[ABCDEFGHKLMNPQS]/.test(value)) // Es una letra de las admitidas ?
			return false;
		else
			{
			//Comprobamos dígito de control
			for( i = 2; i <= 6; i += 2 ) 
				{
				temp = temp + v1[ parseInt(value.substr(i-1,1))];
				temp = temp + parseInt(value.substr(i,1));
				};
			
			temp = temp + v1[ parseInt(value.substr(7,1))];
			temp = (10 - ( temp % 10));
			
			dc=value.charAt(8);	//Cogo D.C.
			if( temp == 10 )
				{
				//J ó 0
				if (dc!='J' || dc!='0') return false;
				}
			else
				{
				//digito control temp o 64+temp
				tempL=String.fromCharCode(64+temp);
				temp=temp.toString();
				if (dc!=temp && dc!=tempL) return false;
				};
			};
			
	return true;
};


/***********************************************************************************************
 Comprueba que un valor contenga un NIF valido. True si OK, false si no
 (tipo DDDDDDDDL	8 digitos - letra )
***********************************************************************************************/
function FRMCheckNIF(value)
{
var RE=/^\d{8}[TRWAGMYFPDXBNJZSQVHLCKE]$/;

	//Comprobamos que lo que venga tenga el formato adecuado (8 dígitos + 1 letra)
	if (!RE.test(value)) return false;

var dni=Math.floor(value.substr(0,8));
var letras = 'TRWAGMYFPDXBNJZSQVHLCKE';
var numero = dni%23;
var letra = letras.substring(numero,numero+1);

	if (value.charAt(8)!=letra) return false;	//Si la letra no es correcta, false
	return true;
};


/***********************************************************************************************
 Se le pasa una referencia a un formulario y comprueba todos los campos
 atendiendo a la clase CMPCampo
************************************************************************************************/
function FRMValidateCmpForm(frm)
{
var j,FRC,t;
var frmCmp=frm.elements;

	//Inicializa todos los datos
	for (j=0;j<frmCmp.length;j++)
		{
		FRC=FRMCmps[frmCmp[j].name];	//Me busco a ver si tiene referencia a la clase de campos
		if (FRC)
			{
			//La tiene, asi que llamo a su funcion de validar
			if (!FRC.validate())
				{
				FRC.obj.focus();
				return -1;
				};
			};
		};
	
	return 0;
};
