/* JavaScript Document
*
* Gestion des erreurs de saisie
*
* Stéphane AIMAR
* @afij.org
* 
* Pour être utiliser il faut :
*   - /scripts/fonction.js (fonctions basiques)
*   - /css/gestion_erreurs.css (CSS pour les tooltips d'erreur et GE_erreurSaisie et GE_normalSaisie)
*   - un CSS définissant "GE_normalSaisie" et "GE_erreurSaisie"
*
* Syntaxe :
*	<input type="text" name="INPUT" id="INPUT">
*
*
*
* Paramètres de loadVariables :
*
*	ID						: id (au sens HTML) de l'erreur
*	VALUE/FORMAT			: valeur a respecter (vide ou expression régulière ou DATE, HEURE, MAIL, INT, FLOAT)
*	ERROR_TEXT				: message d'erreur
*	LISTE_CARS				: liste de caractères (expression régulière) valide ou invalide si ! présent en début d'expression régulière 
*	DISABLE_AUTO_VERIF		: desactive la vérif auto en passant par GE.checkAllErrors
*	X_POS					: décalage en X du label (AUTO permet de positionner tout seul, spécifier l'emplacement au cas ou pb)
*	CSS_ADDON				: Ajouts au style CSS en plus de celui par défaut par exemple en fonction du type "int" text align a droite

 Pour Opera, sur la vérification des touches clavier il faut ajouter la récupération de l'évènement onKeyDown car il renvoie les keycode des fleches sur l'évènement onKeyPress

*/

	var _GE_aErrorArray = new Array();
	var _GE_className = "GE_normalSaisie";
	var _GE_errorClassName = "GE_erreurSaisie";
	var _GE_ToolTipClassName ="GE_cssToolTipErrorDisplay";
	
	var _GE_sAncre_name = "_ANCH_ERREUR";		// n'est plus utilisé
	var _GE_sAncre = "";						// nom de l'ancre a laquelle faire scroller la page
	var _GE_TimeOutFade;						// Variable d'un fade pour pouvoir l'annuler
	var _keyCode = null;
	var _GE_varLoaded = false;					// Permet de savoir si les variables ont été chargées ou pas

	// Gestion Erreurs
	var GE = {
		loadVariables: function (aListe) {
			i=0;
			for (iID in aListe) {
				sID = aListe[iID][0];
				// On vérifie bien que le controle existe
				if (obj = document.getElementById(sID)) {
					if (aListe[iID][4] != true)
						aListe[iID][4] = false;

					if ((obj.parentNode) && ((aListe[iID][5] == "AUTO") || (aListe[iID][5] == null)))
						iXpos = obj.offsetLeft - obj.parentNode.offsetLeft + 10;
					else
						iXpos = aListe[iID][5];

					_GE_aErrorArray[sID] = {
						ID: sID,
						VALUE: aListe[iID][1],
						ERROR_TEXT: aListe[iID][2],
						LISTE_CARS: aListe[iID][3],
						DISABLE_AUTO_VERIF: aListe[iID][4],
						X_POS: iXpos,
						FORMAT: aListe[iID][1],
						CSS_ADDON: new Array()
					};
//					,
//						DEFAULT_TEXT: aListe[iID][6]
					
					var aClass = new Array();	// Tableau regroupant les différents styles a appliquer
					
					// Récupération du champ par son ID
					fieldElement = document.getElementById(sID);
					
					//Si il y a un texte d'erreur c'est qu'il y a gestion d'erreur si non, on n'ajoute rien
					if (aListe[iID][2])  {

						// Analyse du champs VALUE qui peut comporter une expression régulière du format de réponse souhaité
						switch (_GE_aErrorArray[sID].FORMAT) {
							case "STRING":
									_GE_aErrorArray[sID].VALUE = "(.+)";
									_GE_aErrorArray[sID].LISTE_CARS = null;
								break;
							case "CP":
									_GE_aErrorArray[sID].VALUE = "([0-9]{0,5})";
									_GE_aErrorArray[sID].LISTE_CARS = "[0-9]";
								break;
							case "DATE":
									_GE_aErrorArray[sID].VALUE = "[0-9]{2}/[0-9]{2}/[0-9]{4}";
									_GE_aErrorArray[sID].LISTE_CARS = "([0-9/])";
								break;
							case "HEURE":
									_GE_aErrorArray[sID].VALUE = "[0-9]{1,2}[Hh]{1}[0-9]{0,2}";
									_GE_aErrorArray[sID].LISTE_CARS = "([0-9hH])";
								break;
							case "MAIL":
									_GE_aErrorArray[sID].VALUE = "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?"; // changement de regex celuici fait planter sur les mails trop longs ! "^[\_]*([a-zA-Z0-9]+(\.|\_*)?)+@([a-zA-Z0-9][a-zA-Z0-9\-]*(\.|\-*\.))+[a-zA-Z]{2,6}$";
									_GE_aErrorArray[sID].LISTE_CARS = "([a-z0-9\.|\_@-])";
								break;
							case "URL":
									_GE_aErrorArray[sID].VALUE = "(http|https)(://)([a-zA-Z0-9/.-_?=&%+]+)"; // filtre grossier
									_GE_aErrorArray[sID].LISTE_CARS = "([a-z0-9/._?=&%+:-])";
								break;
							case "INT":
									_GE_aErrorArray[sID].VALUE = "^\\d+$";
									_GE_aErrorArray[sID].LISTE_CARS = "([0-9])";
									aClass.push("int");	// Ajout du style int au CSS
								break;
							case "FLOAT":
									_GE_aErrorArray[sID].VALUE = "^\\d*\.{0,1}\\d+$";
									_GE_aErrorArray[sID].LISTE_CARS = "([0-9.])";
									aClass.push("int"); // Ajout du style int au CSS
								break;
						}
	
						// Création du div d'erreur associé, il faut que l'élément soit dans un div ou li mais pas un spam ou un td (non reconnu par IE bug millénaire...)
						errorElement = document.createElement("div");
						errorElement.setAttribute("id", sID+"_error");
						errorElement.className = _GE_ToolTipClassName;
						// Ajout du div d'erreur après l'élément en se servent de l'élement parent du champ
						fieldElement.parentNode.appendChild(errorElement);

						// Mise en place des évènements
						_GE_setEvents(sID);
						
						if (_GE_aErrorArray[sID].LISTE_CARS) {
							var Expression = new RegExp("^!(.*)");
							
							// pour Opera qui ne reconnais pas bien la différence entre les touches fléchées et &(... au keyPress, je suis obligé de récupérer l'évènement keydown
							document.getElementById(sID).onkeydown = _GE_keyDownEvent;					
							
							// Si l'expression régulière commence par un ! (not) c'est une liste de caractères invalides
							if (Expression.test(_GE_aErrorArray[sID].LISTE_CARS)) {
								_GE_aErrorArray[sID].LISTE_CARS = Expression.exec(_GE_aErrorArray[sID].LISTE_CARS)[1];
								fieldElement.onkeypress = _GE_invalidsCars;
							} else // Si non c'est une liste de caractères valides
								fieldElement.onkeypress = _GE_validsCars;
						}
					}
					
					_GE_aErrorArray[sID].CSS_ADDON = aClass;	// On ajoute les types css en plus dans la liste des propriétés de l'objet
					
					aClass.push(_GE_className);	//Ajout du style d'erreur par défaut
					// Application du style CSS en concaténant les données du tableau avec un espace
					fieldElement.className = aClass.join(" ");
				}
			}
			_GE_varLoaded = true; // Les variables ont été chargées
		}, 
		
		checkAllErrors: function (bSendError) {
			// Vérifie touts les champs, sauf ceux sans erreurs (TEXT) et/ou avec DISABLE_AUTO_VERIF a false
			var sErrors = "";
			_GE_sAncre = "";
			
			if (!_GE_varLoaded)
				return "Veuillez attendre quelques secondes que le formulaire sont entièrement chargé, merci.";

			for (iID in _GE_aErrorArray) {
				if (_GE_aErrorArray[iID].ERROR_TEXT != null)
					if ((!_GE_aErrorArray[iID].DISABLE_AUTO_VERIF))
						sErrors += _GE_checkError(iID, false);
			}
			
			if (bSendError)
				return sErrors;
			else
				return (sErrors == "");
		},
		
		checkError: function (sId) {
			return _GE_checkError(sId, false);
		},
		
		forceCheckError: function (sId) {
			return _GE_checkError(sId, true);
		},
		
		raiseError: function (sId, bColorInput, sErreur) {
			return _GE_raiseError(sId, bColorInput, sErreur);
		},
		
		closeError: function (sId, bColorInput) {
			_GE_closeError(sId, bColorInput);
		},
		
		getAncre: function () {
			return _GE_sAncre + _GE_sAncre_name;
			_GE_sAncre = "";
		},
/*
		validsCars: function (iID, eEvent) {
			return _GE_validsCars(iID, eEvent);
		},
		
		invalidsCars: function (iID, eEvent) {
			return _GE_invalidsCars(iID, eEvent);
		},
*/
		keyDownEvent: function (event) {
			return _GE_keyDownEvent (event);
		},
		
		getID: function (iNum) {
			return _GE_aErrorArray[iNum].ID;
		},
		
		checkHeure: function (sId, bColorInput) {
			return _GE_checkHeure(sId, bColorInput);
		},
		
		checkDate: function (sId, bColorInput) {
			return _GE_checkDate(sId, bColorInput);
		},
		
		checkPeriode: function (sIdFrom, sIdTo) {
			return __GE_checkPeriode(sIdFrom, sIdTo);
		},
		
		setAutoVerif: function (sId, bAutoVerif) {
			_GE_setAutoVerif(sId, bAutoVerif);
		},
		
		scrollTo: function () {
			_GE_scrollTo();
		}
	};


	function _GE_autoShowError() {
		_GE_checkError (this.id, false);
	}

	function _GE_hideError() {
		if (this.id) {
			var objError = document.getElementById(this.id+"_error");
			objError.innerHTML="";
			objError.style.display="none";
		}
	}

	/**
	 * récupère le keycode au keydown, utilisé pour opera qui ne fait pas la différence entre fleches et &(' sur le keypress 
	 * @param {Object} event
	 */
	function _GE_keyDownEvent(event) {
		if (window.event)
		   _keyCode = window.event.keyCode;
		else if (event)
		   _keyCode = event.which;
		else
		   return null;
		   
	}

// Fonction de test de valeur de saisie faisant apparaitre une erreur
	/**
	 * Vérifie le contenu d'un input en fonction des l'expression regex déclarée ou du type DATE/HEURE
	 * @param {Object} sID
	 */
	function _GE_checkError(sID, bOverride) {
		
		var valError	= _GE_aErrorArray[sID].VALUE;
		var sErreur		= _GE_aErrorArray[sID].ERROR_TEXT;
		var obj			= document.getElementById(sID);
		var key;
		
		if (valError=="")
			Expression = new RegExp("(.+)");
		else
			Expression = new RegExp(valError);
		
		obj.value = trim(obj.value);

		if ((!_GE_aErrorArray[sID].DISABLE_AUTO_VERIF) || (_GE_aErrorArray[sID].DISABLE_AUTO_VERIF && (obj.value != "")) || bOverride) {
			switch (_GE_aErrorArray[sID].FORMAT) {
				case "DATE":
						return _GE_checkDate(sID, true);
					break;
				case "HEURE":
						return _GE_checkHeure(sID, true);
					break;
				default :
					if (!Expression.test(obj.value)) {
						return _GE_raiseError(sID, true);
					} else {
						_GE_closeError(sID, true);
						return "";
					}
			}
		} else {
			_GE_closeError(sID, true);
			return "";
		}

	 };

	/**
	 * Affiche l'erreur associée a l'input d'ID sID
	 * @param {Object} sID
	 * @param {Object} bColorInput
	 */
	function _GE_raiseError(sID, bColorInput, sTextError) {
		var sErreur		= _GE_aErrorArray[sID].ERROR_TEXT;
		
		if (sTextError)
			sErreur = sTextError;

		if (_GE_sAncre == "")
				_GE_sAncre = sID;

		_GE_showError(sID, sErreur, bColorInput);

		return "- "+sErreur+"\n";
	}

	/**
	 * Vérifie si le caractère frappé est interdit
	 * @param {Event} eEvent Evènement
	 */
	function _GE_invalidsCars(eEvent) {
		var iID = this.id;
		var Expression = new RegExp(_GE_aErrorArray[iID].LISTE_CARS);
		var ObjID = _GE_aErrorArray[iID].ID;
		var bTextFormat = false;
		var key;

		if (eEvent!=null) {
			if (window.event)
				key = window.event.keyCode;
			else if (eEvent)
				key = eEvent.which;
			else
				return true;
			sValue = String.fromCharCode(key);
		} else {
			key=1;
			bTextFormat=true;
			sValue = document.getElementById(ObjID).value;
		}

	// Pour Opera qui renvoie un keycode équivalent aux fleches lors de l'appui sur &'( onbligé de voir le keycode de l'évènemen keydown
		if ((key >= 33) && (key <= 40) && (_keyCode != null) && (key == _keyCode))
				return true;
		
		if ((key!=0) && (key!=8) && (key!=9) && (key!=13) && (key!=27)) {
			
//			alert (key+"/"+keychar);
			if (Expression.test(sValue)){
				if (!bTextFormat)
					_GE_showError(iID, "Caract&egrave;re&nbsp;'<strong>"+String.fromCharCode(key)+"</strong>'&nbsp;interdit", true, true);
				else
					_GE_raiseError(iID);
				return false;
			} else
				_GE_closeError(iID, true);
		}
		return true;
	}

	/**
	 * Vérifie si le caractère frappé est autorisé
	 * @param {Event} eEvent Evènement
	 */
	function _GE_validsCars(eEvent) {
		var iID = this.id;
		var Expression = new RegExp(_GE_aErrorArray[iID].LISTE_CARS);
		var key;

		if (window.event)
		   key = window.event.keyCode;
		else if (eEvent)
		   key = eEvent.which;
		else
		   return true;
/*
		if ((_keyCode != null) && (_keyCode != key))
			key = _keyCode;
*/
	// Pour Opera qui renvoie un keycode équivalent aux fleches lors de l'appui sur &'( onbligé de voir le keycode de l'évènemen keydown
		if ((key >= 33) && (key <= 40) && (_keyCode != null) && (key == _keyCode))
				return true;

		if ((key!=0) && (key!=8) && (key!=9) && (key!=13) && (key!=27)) { // && (key!=37) && (key!=38) && (key!=39) && (key!=40)) {
			keychar = String.fromCharCode(key);
//	alert (_keyCode+"/"+key+"/"+keychar+"->"+Expression.test(keychar))
			if (!Expression.test(keychar)){
				_GE_showError(iID, "Caract&egrave;re&nbsp;'<strong>"+keychar+"</strong>'&nbsp;interdit", true, true);
				return false;
			} else
				_GE_closeError(iID, true);
		}
		return true;
	}

	/**
	 * Affiche l'erreur corespondante à sID
	 * @param {String} sID ID de l'erreur
	 * @param {String} sTextError Texte à afficher pour erreur
	 * @param {Boolean} bColorInput colorer l'input si il y a erreur true/false
	 * @param {Boolean} bCloseFade faire disparaitre l'erreur de façon progressive true/false
	 */
	function _GE_showError (sID, sTextError, bColorInput, bCloseFade) {
		var objErrorID = sID+"_error";
		var elemErreurDisplay = document.getElementById(objErrorID);
		var sHtmlTextError = "";

		_GE_aErrorArray[sID].fadeState = 0;

		if (_GE_TimeOutFade)
			clearTimeout(_GE_TimeOutFade);

		sHtmlTextError = sTextError.replace(/ /g, "&nbsp;");

		if (_GE_aErrorArray[sID].X_POS)
			elemErreurDisplay.style.left = _GE_aErrorArray[sID].X_POS+"px";

		sHTML = "<div><span>"+sHtmlTextError+" <a class=\"GE_BnClose\" alt=\"fermer\" href=\"javascript:GE.closeError('"+sID+"');\">X Fermer</a></span></div>";
		elemErreurDisplay.innerHTML = sHTML;
		
		elemErreurDisplay.style.opacity = "1";
		elemErreurDisplay.style.display = "block";

		if (bColorInput)
			document.getElementById(sID).className = _GE_errorClassName;

		// Si on ferme en disparaissant			
		if (bCloseFade)
			_GE_TimeOutFade = setTimeout("_GE_FadeError('"+sID+"')", 4000);
	}

	/**
	 * Fonction qui change l'opacité
	 * @param {String} sID ID de l'erreur
	 */
	function _GE_fadeElem(sID) {
		var elem = document.getElementById(sID+"_error");
		if (((elem.style.opacity >0) && (elem.style.opacity <1)) && _GE_aErrorArray[sID].fadeState == 1) {
			elem.style.opacity -= 1/100;
			setTimeout("_GE_fadeElem('"+sID+"')", elem.style.opacity*10);
		} else {
			_GE_aErrorArray[sID].fadeState = 0;
			elem.style.display = "none";
		}
			
	}
	
	/**
	 * Fait disparaitre progressivement une erreur
	 * @param {String} objID ID de l'erreur
	 */
	function _GE_FadeError(sID){
		var elem = document.getElementById(sID+"_error");
		_GE_aErrorArray[sID].fadeState = 1;

		elem.style.opacity = 0.99;
		_GE_fadeElem(sID);
	}

	/**
	 * Ferme un tooltip d'erreur
	 * @param {Object} iErreurIndex
	 * @param {Object} bColorInput
	 */
	function _GE_closeError(iErreurIndex, bColorInput) {
		var objErrorID	= _GE_aErrorArray[iErreurIndex].ID+"_error";
		var objError	= document.getElementById(objErrorID);

		if (objError.style.display != "none") {
			objError.innerHTML="";
			objError.style.display="none";
		}
		
		if (bColorInput) {
			aColor = new Array();
			aColor = _GE_aErrorArray[iErreurIndex].CSS_ADDON;
			aColor.push(_GE_className);
			document.getElementById(_GE_aErrorArray[iErreurIndex].ID).className = aColor.join(" ");
		}

	}


	/**
	 * Vérifie le format d'une heure
	 * @param {Object} objID ID de la textbox contenant l'information
	 * @param {Boolean} bColorInput Colore ou pas la textbox si il y a erreur
	 */
	function _GE_checkHeure(objID, bColorInput) {
		sHeure = document.getElementById(objID).value;
		var Expression = new RegExp("([0-9]{1,2})[Hh]{1}([0-9]{0,2})");
		aHeure = Expression.exec(sHeure);

		if (trim(sHeure) == "")
			return _GE_raiseError(objID, true);

		if (!aHeure)
			return _GE_raiseError(objID, true, "Format incorrect <strong>##H##</strong>");

		if (((aHeure[1]>=0) && (aHeure[1]<24)) && (((aHeure[2]>=0) && (aHeure[2]<60)) || (!aHeure[2]))){
			_GE_closeError(objID, bColorInput);
			return "";
		}else
			return _GE_raiseError(objID, true, "Heure invalide");
	}

	/**
	 * Vérifie le format d'une date
	 * @param {Object} objID ID de la textbox contenant l'information
	 * @param {Boolean} bColorInput Colore ou pas la textbox si il y a erreur
	 */
	function _GE_checkDate(objID, bColorInput) {
		sDate = document.getElementById(objID).value;
		var Expression = new RegExp("([0-9]{1,2})/([0-9]{1,2})/([0-9]{4})");
		aDate = Expression.exec(sDate);

		if (trim(sDate) == "")
			return _GE_raiseError(objID, true);

		if (!aDate)
			return _GE_raiseError(objID, true, "Format incorrect <strong>JJ/MM/AAAA</strong");

		dDateTest = new Date(aDate[3], aDate[2]-1, aDate[1]); // année, mois (0-11), jour

		iAnnee = dDateTest.getYear();
		if ((Math.abs(iAnnee)+"").length < 4) iAnnee = iAnnee + 1900

		if ((dDateTest.getDate() == eval(aDate[1])) && (dDateTest.getMonth() == eval(aDate[2])-1) && (iAnnee == eval(aDate[3]))) {
			_GE_closeError(objID, bColorInput);
			return "";
		}else
			return _GE_raiseError(objID, true, "Date incorrecte");
			
	}


	function __GE_checkPeriode (sIdFrom, sIdTo) {
		sDateFrom = document.getElementById(sIdFrom).value;
		aDateFrom = sDateFrom.split('/');
		dDateFrom = new Date(aDateFrom[2], aDateFrom[1], aDateFrom[0]);
		
		sDateTo = document.getElementById(sIdTo).value;
		aDateTo = sDateTo.split('/');
		dDateTo = new Date(aDateTo[2], aDateTo[1], aDateTo[0]);

		if (dDateTo.getTime() < dDateFrom.getTime())
			return _GE_raiseError(sIdTo, true, "Période incorrecte");
		else
			return "";

	}

	/**
	 * Supprime les espace en trop au début et à la fin
	 * @param {String} string Chaine à traiter
	 */
	function trim(string) {
		return string.replace(/(^\s*)|(\s*$)/g,'');
	} 

	/**
	 * Permet de modifier le statu de l'auto vérification d'un textbox
	 * @param {String} sId ID du champ
	 * @param {Boolean} bAutoVerif Vérification automatique true/false
	 */
	function _GE_setAutoVerif(sId, bAutoVerif) {
		_GE_aErrorArray[sId].DISABLE_AUTO_VERIF = !bAutoVerif;
		_GE_setEvents(sId);
	}
	
	/**
	 * Ajoute ou enlève les évènements liés au champ en fonction de NO_AUTO_VREFI
	 * @param {Object} sId ID du champ
	 */
	function _GE_setEvents(sId) {
		oElem = document.getElementById(sId);

		// Evènements classiques
		oElem.onfocus = _GE_hideError;

		// Evènements spéciaux
		if (_GE_aErrorArray[sId].DISABLE_AUTO_VERIF != true) {
			switch (oElem.type) {
				case "text" :
				case "password":
				case "textarea" :
						oElem.onblur = _GE_autoShowError;
					break;
				case "select-one" :
						oElem.onblur = _GE_autoShowError;
						oElem.onchange = _GE_autoShowError;
					break;
			}
		} else {
			_GE_closeError(sId, true);
			switch (oElem.type) {
				case "text" :
				case "password":
				case "textarea" :
						oElem.onblur = _GE_autoShowError;
					break;
				case "select-one" :
						oElem.onblur = null;
						oElem.onchange = null;
					break;
			}
		}
		
	}
	
	/**
	 * Permet de faire scroller le navigateur directement vers le (1er si pas de paramètres) champ qui pose problème 
	 * @param {Object} sId ID du champ
	 */
	function _GE_scrollTo(sID) {
		if (!sID)
			sID = _GE_sAncre;
		document.getElementById(sID).scrollIntoView("true")
	}

