
/**
* Ce fichier contient l'implémentation d'une completion MEV.
* Il nécessite l'utilisation du fichier completion.js (qui est mutuel aux version MEV, MEH...)
* Au début de ce fichier on trouvera notemmment les variables globales completionMEVURLRoot et completionMEVRJS. On devra s'assurer que ces variables
* contiennent des valeurs convenables.
* NOTA: Ce script nécessite l'utilisation du script tools.js qui doit donc être invoqué dans la page, ainsi que du fichier CSS par défaut 
* completionMEV.css
* 
* L'implémentation de la completion MEV est alors la suivante:
* La page HTML disposant de la completion pour la détermination doit impérativement contenir les trois éléments suivants (pour chaque lieu à 
* déterminer):
* - Un champ texte de saisie
* - Un div vide qui permet le positionnement des propositions
* - Un champ stockant les données 'data' sélectionnées par l'internaute après sélection d'une proposition
* 
*
* Rappel sur les URL de MEV: Les deux URL suivantes sont valides, et correpondent à une recherche Paris/Oslo
* 
* Voici l'url d'une recherche ne tenant compte que de la saisie texte de l'internaute (il n'a pas sélectionné d'élément dans la liste des propositions
* d'autocompletion).
* http://www.easyvols.fr/mev/results.jsp?departAller=paris&departAllerData=&arriveeAller=oslo&arriveeAllerData=&...
* 
* Voici maintenant l'url d'une recherche où l'internaute a sélectionné Paris et Oslo dans les listes de propositions d'autocompletion
* http://www.easyvols.fr/mev/results.jsp?departAllerData=v%3A5580&departAller=Paris&arriveeAllerData=v%3A5489&arriveeAller=Oslo&...
* 
* On remarque que dans le premier cas les paramètres departAller et arriveeAller contiennent les textes qui vont servir à la recherche, ils 
* correspondent au champ texte de saisie évoqué plus haut, les paramètres departAllerData et arriveeAllerData sont vides. Cela n'a pas d'importance, 
* les paramètres departAller et arriveeAller vont être lus pour déterminer le parcours.
*
* Dans le deuxieme cas on constate que les même champs departAller et arriveeAller contiennent des textes qui ont été modifiés lors de la sélection des
* propositions associées (ce ne sont plus les textes tapés par l'internaute) en revanche les champs dataDepartAller et dataArriveeAller contiennent
* les données 'data' correctes. Ces champs correspondent en fait au champ data cité plus haut qui est usuellement un champ hidden. Dans ce cas, les textes
* de departAller et arriveeAller sont inexploitables, mais en fait c'est les champs data qui ayant la priorité vont permettre de déterminer correctement
* le parcours.
* Le contenu de data sera généralement v:VILLE (VILLE étant un identifiant unique numérique de ville), a:AEROPORT (AEROPORT étant un identifiant unique
* numérique d'aéroport) ou l:ADRESSE (ADRESSE étant un identifiant unique numérique d'adresse)
* Le contenu de data pourra aussi être un code IATA. (c:IATA ou IATA ; pour assurer la compatibilité avec l'ancien système de paramètrage des recherches)
*
* Par exemple pour implémenter la completion sur le lieu de départ aller, on se retrouvera typiquement avec les éléments HTML suivants:
* 
* 	<input type="text" id="departAllerMEV" name="departAller"><!-- champ texte de saisie -->
*	<div id="propositionsDepartAller"><!-- Les propositions seront affichées ici --><div>
* 	<input type="hidden" id="departAllerMEVData" name="departAllerData"><!-- champ hiden data -->
*
* La dernière chose à faire pour rendre la completion disponible, est d'instancier l'objet CompletionMEV en lui passant les références aux éléments 
* précédents. Ceci peut être fait par l'attribut onload du tag body
* 	new CompletionMEV(document.getElementById("departAllerMEV"),document.getElementById("departAllerMEVData"),document.getElementById("propositionsDepartAller"));
* 	on peut optionnellement, dans la construction précédente, rajouter un dernier paramètre qui détermine le nombre de villes qui vont apparaitre
* 	dans les listes de propositions.
* Pour déterminer le comportement css, se référer à la structure HTML définie en commentaire de la classe CompletionMEV plus bas.
**/


var dn="www.easyvols.org";
//var dn="seoul.mev.lfr:85";
// Constante globale qui identifie l'URL racine des fichiers rjs. Il faut vérifier que la source ici définie, correspond effectivement à une 
// localisation valable de fichiers rjs.
var completionMEVURLRoot;
var TOUS_AEROPORTS;
switch(window.lang){
	case "es_ES":
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/es_ES";
		TOUS_AEROPORTS="todos aeropuertos";
		break;
	case "it_IT":
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/it_IT";
		TOUS_AEROPORTS="tutti aeroporti";
		break;
	case "en_GB":
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/en_GB";
		TOUS_AEROPORTS="all airports";
		break;
	case "de_DE":
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/de_DE";
		TOUS_AEROPORTS="alle Flughäfen";
		break;
	case "fr_FR":
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/fr_FR";
		TOUS_AEROPORTS="tous aéroports";
		break;
	default:
		completionMEVURLRoot="http://"+dn+"/base/rjs/completion/MEV/fr_FR";
		TOUS_AEROPORTS="tous aéroports";
		break;
}

// Constante globale l'instance RemoteScript qui sera utilisée par toutes les instances de CompletionMEV 
var completionMEVRJS=new RemoteScript(completionMEVURLRoot+"/load_default.rjs");

// Hauteur des lignes pour les MSIE<=6.9
var MSIE6_LINE_HEIGHT=20;
var MSIE6_LINE_WIDTH=300;



/**
 * 
 * @param {Object} _string
 */
function extractTypeFromData(_string) {					
	var t1        =  _string.split('|'),			
	t2        = t1[0].split(':'),			
	typeLieu  = t2[0];
	if(!typeLieu) {
		return '';
	}			
	return typeLieu;
}

/**
 * 
 * @param {Object} _selectedLine
 * @param {Object} _towns
 */	
function getTown(_selectedLine,_towns) {			
	var nom = _selectedLine.nom;
	for(var i=0;i<_towns.length;i++){									
		for(var j=0;j<_towns[i].aeroports.length;j++){
			if(_towns[i].aeroports[j].nom == nom) {		// FIXME					
				return  _towns[i];
			}
		}
	}				
}
	
/**
 * 
 * @param {Object} _towns
 * @param {Object} _selectedLine
 */	
function setLineData(_elementText,_towns,_line) {		
	if(extractTypeFromData(_line.data) == 'a') {										
		var town = getTown(_line,_towns);					 		
		_elementText.setAttribute('vdata',town.data);									
		_elementText.setAttribute('adata',_line.data);	
																																		
	}else {
		_elementText.setAttribute('vdata',_line.data);
		_elementText.removeAttribute('adata');	
	}			
}	





/**
* Objet qui gère l'autocompletion MEV, en utilisant le texte saisi dans le champ texte qui lui est associé, affiche les propositions dans le div
* de positionnement qui lui est affecté, et en fonction de la sélection effectuée par l'internaute modifie le champ data qui lui est associé.
* - elementText: champ texte de saisie
* - elementData: champ data
* - positionDiv: div de positionnement des propositions
* - maxTownsInList: (optionnel) limite le nombre de villes affichées dans une liste de propositions
* - townRequest true ajoute vdata contenant le champs data de la selection si ville, et vdata et adatacontenant le champs data de la selection si un aeroport. 
* La structure HTML générée pour afficher les propositions est la suivante:
* elle s'inscrit complètement dans le div de positionnement (je reprends la dénomination proposée en commentaire liminaire où le positionnement
* des propositions est assuré par le div propositionsDepartAller.
* 	<!-- ce div sert donc à positionner l'affichage à un endroit précis de la page il est passé à l'instance par le paramètre positionDiv, il n'est 
* 	     pas référencé en tant que propriété 
* 	-->
* 	<div id="propositionsDepartAller">
* 		<!-- ce div est créé à l'instanciation de CompletionMEV, il est référencé par la propriété this.propositionsDiv. Les méthodes 
* 		     this.showPropositions() et this.hidePropositions() utilisent son style visibility pour faire apparaitre et disparaitre les propositions. 
* 		     Il est affecté de la classe CSS completion qui de préférence va définir un positionnement absolute (afin de donner à la liste de 
* 		     propositions l'apparence d'une fenêtre flotante, et d'éviter qu'elle ne pousse vers le bas les éléments qui se trouvent sous elle,
* 		     mais il n'est pas interdit de faire autre chose... Je recommande aussi de définir un fond pour que la liste ne soit pas transparente!
* 		-->
* 		<div class="completionMEV">
* 			<!-- Chaque ville dispose alors d'un div propre, que cette ville dispose ou non d'aéroports ces div sont affectés de la classe 
* 			     propositionVille-->
* 			<div class="propositionVille">Ciudad del Este, Paraguay</div>
* 			<div class="propositionVille">Asuncion, Paraguay</div>
* 			<!-- Les villes disposant en plus d'aéroports voient leur div contenir des div supplémentaires -->
* 			<div class="propositionVille">
* 				Paris, France
* 				<div class="propositionAeroport">Beauvais-Tille</div>
* 				<div class="propositionAeroport">Ch. De Gaulle</div>
* 				<div class="propositionAeroport">Orly</div>
* 			</div>
* 			<div class="propositionVille">Paros, Grèce</div>
* 			<!-- l'élément sélectionné se voit affecté la classe selected qui peut être déclarée dans la feuille de style sous la dénomination
* 			     .completionMEV .selected pour ne pas faire mélange avec d'autres selected. En revanche il peut être nécessaire de surcharger une 
* 			     classe .selected plus générale qui aurait été définie par ailleurs.
* 			-->
* 			<div class="propositionVille selected">
* 				Nassau, Bahamas
* 				<div class="propositionAeroport">Paradise Island</div>
* 			</div>
* 		</div>
*		<!-- Pour IE 6 <iframe frameborder="0"></iframe> -->
* 	</div>
**/
function CompletionMEV(elementText,elementData,positionDiv,maxTownsInList,townRequest) {
	// On référence l'instance dans le pool
	this.poolIndex=completionMEVPool.add(this);
	// On définit l'état de base du panneau de propositions à "off"
	this.status="off";
	// On référence l'élément text et on wrappe les événements onfocus, onblur, onkeydown et onkeyup sur les méthodes appropriées
	this.elementText=elementText;
	// On applique l'attribut autocomplete="off" au champ input pour bloquer l'autocompletion du navigateur
	this.elementText.setAttribute("autocomplete","off");
	// IE ne supporte pas le mot-clé 'return' hors des méthodes
	if(genericNavigator.navigator.id==MSIE){
		addJavascriptToAttribute(this.elementText,"onkeydown","completionMEVPool.get("+this.poolIndex+").toucheDown(event);");
		addJavascriptToAttribute(this.elementText,"onkeyup","completionMEVPool.get("+this.poolIndex+").toucheUp(event);");
		addJavascriptToAttribute(this.elementText,"onfocus","completionMEVPool.get("+this.poolIndex+").updatePropositions(event);");
		// Sur la ligne suivante, il est indispensable de délayer le masquage pour faire en sorte que le onblur ne provoque pas un hidePropositions
		// trop rapide, dans le cas où justement le onblur serait provoqué par un onclick dans l'une de ces propositions. Dans un tel cas, l'absence de 
		// delai d'attente ne permet pas le clic avant la disparition des propositions
		addJavascriptToAttribute(this.elementText,"ondeactivate","completionMEVPool.get("+this.poolIndex+").delayedHidePropositions(500);");
	}
	else{
		addJavascriptToAttribute(this.elementText,"onkeydown","return completionMEVPool.get("+this.poolIndex+").toucheDown(event);");
		addJavascriptToAttribute(this.elementText,"onkeyup","return completionMEVPool.get("+this.poolIndex+").toucheUp(event);");
		addJavascriptToAttribute(this.elementText,"onfocus","return completionMEVPool.get("+this.poolIndex+").updatePropositions(event);");
		// Sur la ligne suivante, il est indispensable de délayer le masquage pour faire en sorte que le onblur ne provoque pas un hidePropositions
		// trop rapide, dans le cas où justement le onblur serait provoqué par un onclick dans l'une de ces propositions. Dans un tel cas, l'absence de 
		// delai d'attente ne permet pas le clic avant la disparition des propositions
		addJavascriptToAttribute(this.elementText,"onblur","completionMEVPool.get("+this.poolIndex+").delayedHidePropositions(500);");
	}
	// On référence l'élément data
	this.elementData=elementData;
	// On référence le div de positionnement des propositions
	this.propositionsDiv=document.createElement("DIV");
	this.propositionsDiv.style.visibility="hidden";
	this.propositionsDiv.className="completionMEV";
	if(genericNavigator.navigator.id==MSIE&&genericNavigator.navigator.version<=6.9){
		this.propositionsDiv.style.width=MSIE6_LINE_WIDTH+"px";
		this.propositionsDiv.style.padding="5px";
	}
	positionDiv.appendChild(this.propositionsDiv);
	
	var DEFAULT_MAX_TOWNS_IN_LIST=6;
	// Nombre maximum de villes affichées dans la liste de propositions
	this.maxTownsInList=DEFAULT_MAX_TOWNS_IN_LIST;
	if(maxTownsInList){
		this.maxTownsInList=maxTownsInList;
	}
	
	// Cette propriété est mise à jour par les rjs et contient l'ensemble des propositions associée au préfixe de la saisie utilisateur
	this.propositions=[];

	// Cette propriété contient la ligne sélectionnée par l'intermédiaire des flêches du clavier. Par défaut aucune ligne n'ayant été sélectionnée
	// cette propriété contient -1. Elle permet un lien immédiat avec les objets référencés dans this.lines
	this.lineSelected=-1;
	// Lorsque la liste de propositions est construite, chaque élémeconvertVilleTextToDivnt affiché est référencé dans l'ordre dans ce tableau, ce qui permet de conserver
	// un ordre d'affichage des données. Il faut se rappeler que les données sont organisées hiérarchiquement entre villes et aéroports, ce tableau
	// linéarise tout cela
	this.lines=[];
	
	// Cette propriété contient la ville sélectionnée par les flêches (mémorise la dernière ville sélectionnée, mais pas les aéroports).
	this.townSelected=0;
	// Cette propriété contient le tableau des villes, uniquement des villes.
	this.towns=[];
	// Cette propriété mainitent la page affichée
	this.pageIndex=0;	
	// Cette propriété sert de jeton, elle permet de savoir si une requête de rjs est en cours avant d'en lancer une autre
	this.updating=false;
	/***/
	var This = this;

	/**
	 * Cette méthode permet lancer la récupération de la liste de propositions.
	 * La valeur de retour permet de préciser si l'on doit laisser remonter l'évenement au niveau du navigateur (toujours oui : true)
	**/
	this.toucheUp=function(event) {
		if(!event){return;}
		if(shouldUpdatePropositions(event)){
			this.updatePropositions(event);
		}
		return true;
	};

	/**
	 * Cette méthode utilise une méthode générique qui traite les déplacements, validations et annulation de proposition(s) à partir du clavier.
	 * Elle gère également les caractères entrés dans le champ texte.
	 * Elle utilise des méthodes et propriétés de l'objet CompletionMEV courant (en cas de besoin).
	 * (Ces méthodes et/ou propriétés sont les mêmes pour les CompletionMEH et CompletionMEC)
	 * La valeur de retour permet de préciser si l'on doit laisser remonter l'évenement au niveau du navigateur (oui : true ; non : false)
	 * Pour IE le système est différent et on doit modifier l'évènement lorsque celui-ci doit être bloqué (car il ne gère pas et n'accepte pas la valeur de retour)
	 * (cf. completionToucheDown(completion, event)
	**/
	this.toucheDown=function(event) {
		if(!event){return;}
		return completionToucheDown(this, event);
	};

	var lock=!1;
	/**
	 * Cette méthode a été wrappée à l'instanciation sur le onfocus de l'élément texte ou appelée à partir du onkeydown. C'est elle qui va "décider" des affichages de 
	 * propositions qui devront être faits (ou non).
	 * Elle retourne toujours true (lorsque l'appel vient de 'onfocus' l'évenement remonte au navigateur).
	**/
	this.updatePropositions=function(event) {
		if(lock){return;}
		if(!event){return;}
		// Si une requête de mise à jour est déjà en cours on sort
		if(this.updating){return;}
		// Sinon, on signale qu'une mise à jour se prépare
		this.updating=true;
		// Toute saisie de caractère invalide la précédente sélection de data
		if(event.type!=="focus"){
			this.elementData.value="";
			/* Mode iterogation par ville les attributs suivants doivent aussi etre netoyés.*/
			if(townRequest) {									
				if(elementText.getAttribute('vdata')) {					
					elementText.removeAttribute('vdata');
				} 				
				if(elementText.getAttribute('adata')) {
					elementText.removeAttribute('adata');
				} 											
			}			
		}
		var elementTextValue=this.elementText.value;
		if(elementTextValue.length>=3){
			var prefix=getPrefix(elementTextValue);
			this.propositions=[];
			// Use closure (definition d'une variable figée sur l'objet courant et disponible)
			var completionObjet=this;
			// definition d'une fonction à executer après invocation distante (utilisant la variable privée completionObjet)
			var onAfterUpdate=function(){
					ev.log.debug('/load_'+prefix+'.rjs reçu : '+completionObjet.propositions.length+' proposition(s)');
					// On signale la fin de mise à jour
					completionObjet.updating=false;
				};
			// invocation distante
			completionMEVRJS.invoke(completionMEVURLRoot+"/load_"+prefix+".rjs","initMEVPropositions("+this.poolIndex+")", onAfterUpdate);
			// L'invocation précédente provoque aussi l'appel à showPropositions, mais de façon asynchrone, si par malheur, elle tardait
			// à venir celle ci-dessous nous assure d'une execution (rapide).
			// Supprimé jusqu'à preuve du contraire
			// this.showPropositions();
		}
		else{
			// Par défaut, on cache les propositions si moins de 3 caractères
			this.hidePropositions();
			ev.log.debug("Par défaut, on cache les propositions si moins de 3 caractères");
			// On signale la fin de mise à jour
			this.updating=false;
		}
		return true;
	};
	
	
	
	/**
	* Cette méthode affiche les propositions en tenant compte de leur effective correspondance avec le texte saisi dans l'élément texte. Elle 
	* lancée en cas de modification de la liste de propositions.
	**/
	this.showPropositions=function() {
		// Si le div de positionnement des propositions n'est pas présent, inutile d'aller plus loin
		if(!this.propositionsDiv){return;}
		// On vide le div de positionnement de tous les éléments précédements affichés
		var node=this.propositionsDiv.lastChild;
		while(node){
			this.propositionsDiv.removeChild(node);
			node=this.propositionsDiv.lastChild;
		}

		// On calcule l'expression régulière qui correspond au texte tapé par l'utilisateur
		var regExp=new RegExp("^"+formateText(this.elementText.value).replace(/\s+/,"\\s+")+".*");

		// Cette variable va tenir le décompte des lignes des éléments affichés (villes + aéroports)
		var line=0;
		// Cette variable va faire le même travail dans le tableau des villes
		var townIndex=0;
		// On réinitialise les tableaux
		this.lines=[];
		this.towns=[];
		var j, height;
		// On passe en revue toutes les propositions villes
		for(var i=0;i<this.propositions.length;i++){
			// Si la proposition ville matche avec le champ texte
			if(this.propositions[i].matches(regExp)){
				// On place le div correspondant dans la liste de propositions
				// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de la ville
				addJavascriptToAttribute(this.propositions[i].div,"onmouseover","completionMEVPool.get("+this.poolIndex+").selectLine("+line+")");
				addJavascriptToAttribute(this.propositions[i].div,"onmousedown","completionMEVPool.get("+this.poolIndex+").validLine("+line+",true)");
				this.propositions[i].index=townIndex;
				// On insère la ville dans le tableau des lignes
				this.lines[line]=this.propositions[i];
				// On insère ma ville dans le tableau des villes
				this.towns[townIndex]=this.propositions[i];
				line++;
				height=MSIE6_LINE_HEIGHT;
				townIndex++;

				// Initialisation des variables utilisé pour l'ajout du synonymes, du IATA et la mise en gras dasn les proposition du texte saisie.
				var reg=new RegExp("("+formateText(this.elementText.value).replace(/\s+/,"\\s+")+")", "gi");
				var chaine = this.propositions[i].div.childNodes[0].nodeValue;
				var chaineSansAccent=replaceAccents(chaine);
				// Affichage du synonyme ?
				chaine = ajoutSynonyme(this.propositions[i], chaine, chaineSansAccent, regExp, reg);
				// Affichage du code ISO du pays si le match dessus est bon.
				if(this.propositions[i].code.match(reg)){
					chaine = this.propositions[i].nom+" ["+this.propositions[i].code+"], "+this.propositions[i].pays;
				}
				// Creation d'une chaine sans accent pour la mise en évidance.
				chaineSansAccent=replaceAccents(chaine);
				// S'il y a au mois 1 aéroport dans la ville, on met à jour le texte en ajoutant " (tous aéroports)"
				if(this.propositions[i].aeroports.length>0){
					chaine=this.ajoutTousAeroports(chaine);
				}
				// Modification du DOM pour mettre en évidance la correspondance entre le texte fourni et celui proposé.
				this.propositions[i].div.childNodes[0].nodeValue="";
				modifDomWhithSearch(chaine, chaineSansAccent, this.elementText.value, this.propositions[i].div, reg);
				this.propositionsDiv.appendChild(this.propositions[i].div);

				if(this.propositions[i].aeroports.length>0){
					// S'il y a au mois 1 aéroport dans la ville, on met à jour le texte en ajoutant " (tous aéroports)"
					// On y ajoute toutes les propositions aéroports éventuellement contenues dans la ville
					for(j=0;j<this.propositions[i].aeroports.length;j++){
						this.propositions[i].div.appendChild(this.propositions[i].aeroports[j].div);
						// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de l'aéroport
						addJavascriptToAttribute(this.propositions[i].aeroports[j].div,"onmouseover","completionMEVPool.get("+this.poolIndex+").selectLine("+line+")");
						addJavascriptToAttribute(this.propositions[i].aeroports[j].div,"onmousedown","completionMEVPool.get("+this.poolIndex+").validLine("+line+",true)");
						this.lines[line]=this.propositions[i].aeroports[j];
						line++;
						height+=MSIE6_LINE_HEIGHT;
					}
				}
				if(genericNavigator.navigator.id==MSIE&&genericNavigator.navigator.version<=6.9){
					this.propositions[i].div.style.height=height+"px";
					this.propositions[i].div.style.width=MSIE6_LINE_WIDTH+"px";
				}
				continue;
			}
			// Si le matching ne se fait pas sur la ville
			// On considère d'emblée que la proposition de ville ne sera pas visible
			var propositionVilleVisible=false;
			// On teste tous les aéroports
			for(j=0;j<this.propositions[i].aeroports.length;j++){
				// Si l'aéroport en cours matche le texte entré par l'utilisateur son div est ajouté au div de la ville, et cette dernière
				// est prévue pour affichage par propositionVilleVisible=true
				if(this.propositions[i].aeroports[j].matches(regExp)){
					// Initialisation des variables utilisé pour l'ajout du synonymes, du IATA et la mise en gras dasn les proposition du texte saisie.
					var reg=new RegExp("("+formateText(this.elementText.value).replace(/\s+/,"\\s+")+")", "gi");
					var chaine = this.propositions[i].aeroports[j].div.childNodes[0].nodeValue;
					var chaineSansAccent=replaceAccents(chaine);
					// Affichage du code IATA de l'aéroport si le match dessus est bon.
					if(this.propositions[i].aeroports[j].code.match(reg)){
						   chaine = this.propositions[i].aeroports[j].nom+" ["+this.propositions[i].aeroports[j].code+"]";
					}
					// Modification du DOM pour mettre en évidance la correspondance entre le texte fourni et celui proposé.
					chaineSansAccent=replaceAccents(chaine);
					this.propositions[i].aeroports[j].div.childNodes[0].nodeValue="";
					modifDomWhithSearch(chaine, chaineSansAccent, this.elementText.value, this.propositions[i].aeroports[j].div, reg);
					this.propositions[i].div.appendChild(this.propositions[i].aeroports[j].div);

					// Si la ville n'a pas déjà été prévue pour insertion
					height=0;
					if(!propositionVilleVisible){
						// On met à jour le texte de la proposition de ville, en ajoutant " (tous aéroports)"
						this.convertVilleTextToDiv(this.propositions[i]);
						// Ajout d'événements de sélection (onmouseover) et validation (onmousedown) de la ville
						addJavascriptToAttribute(this.propositions[i].div,"onmouseover","completionMEVPool.get("+this.poolIndex+").selectLine("+line+")");
						addJavascriptToAttribute(this.propositions[i].div,"onmousedown","completionMEVPool.get("+this.poolIndex+").validLine("+line+",true)");
						this.propositions[i].index=townIndex;
						// On insère la ville dans le tableau des lignes
						this.lines[line]=this.propositions[i];
						// On insère ma ville dans le tableau des villes
						this.towns[townIndex]=this.propositions[i];
						line++;
						height+=MSIE6_LINE_HEIGHT;
						townIndex++;
						propositionVilleVisible=true;
					}
					// Ajout d'un événement de validation (onmousedown) de l'aéroport
					addJavascriptToAttribute(this.propositions[i].aeroports[j].div,"onmousedown","completionMEVPool.get("+this.poolIndex+").validLine("+line+",true)");
					this.lines[line]=this.propositions[i].aeroports[j];
					line++;
					height+=MSIE6_LINE_HEIGHT;
				}
			}
			// Si l'affichage de la proposition ville est prévu par la présence d'un ou plusieurs de ses aéroports
			if(propositionVilleVisible){
				this.propositionsDiv.appendChild(this.propositions[i].div);
				if(genericNavigator.navigator.id==MSIE&&genericNavigator.navigator.version<=6.9){
					this.propositions[i].div.style.height=height+"px";
					this.propositions[i].div.style.width=MSIE6_LINE_WIDTH+"px";
				}
			}
		}
		// Par défaut on n'a pas de ligne sélectionnée (-1), tout action sur les flêches du clavier va faire apparitre la selection grâce aux tests
		// su this.lineSelected dans les méthodes decreaseSelectedPosition() ou increaseSelectedPosition() 
		this.hidePage();
		this.lineSelected=-1;
		this.townSelected=0;
		this.pageIndex=0;	
		this.showPage();

		if(this.propositions.length>0&&this.propositionsDiv&&this.propositionsDiv.lastChild){
			this.status="on";
			showPropositionsElement(this.propositionsDiv, line);
		}
		else{
			this.hidePropositions();
		}
	};

	/**
	 * Cette méthode met à jour le texte de la proposition de ville, en ajoutant " (tous aéroports)" et en transformant le noeud Text en noeud Span.
	 * Elle n'a aucun effet si le premier noeud n'est pas de type TEXT.
	 */
	this.convertVilleTextToDiv=function(villeProp){
		var textNodeVille=villeProp.div.firstChild;
		if(textNodeVille.nodeType==3){
			textNodeVille.nodeValue=this.ajoutTousAeroports(villeProp.nom+", "+villeProp.pays);
			var divNode=document.createElement("div");
			villeProp.div.replaceChild(divNode, textNodeVille);
			divNode.appendChild(textNodeVille);
		}
	};

	/**
	 * Cette méthode met à jour le texte en paramêtre , en ajoutant " (tous aéroports)".
	 */
	this.ajoutTousAeroports=function(chaine){
		chaine = chaine+" ("+TOUS_AEROPORTS+")";
		return chaine;
	};

	/**
	 * Cette méthode cache la liste des propositions.
	 */
	this.hidePropositions=function(){
		//on force le focus sur le champ de la ville qui affichait la completion, NB : FF le fait par defaut, IE non
		this.status="off";
		hidePropositionsElement(this.propositionsDiv);
	};

	/**
	 * Cette méthode permet de délayer le masquage des propositions via un timeout sur la méthode #hidePropositions()
	 */
	this.delayedHidePropositions=function(delay){
		window.setTimeout('completionMEVPool.get('+this.poolIndex+').hidePropositions();', delay);
	};

	/**
	* Cette méthode modifie éventuellement la page de propositions affichées. 
	**/
	this.updatePage=function(){
		// On détermine l'éventuel nouveau pageIndex
		var newPageIndex=Math.floor(this.townSelected/this.maxTownsInList);
		// S'il a changé on cache la page courante et on affiche la nouvelle
		if(this.pageIndex!=newPageIndex){
			this.hidePage();
			this.pageIndex=newPageIndex;
			this.showPage();
		}
		ev.log.info("on est ds updatePage");
	};

	/**
	* Montre les villes de la page courante.
	**/
	this.showPage=function(){
		for(var i=this.pageIndex*this.maxTownsInList;i<(this.pageIndex+1)*this.maxTownsInList&&i<this.towns.length&&i>=0;i++){
			this.towns[i].div.style.display="block";
		}
	};

	/**
	* Cache les villes de la page courante.
	**/
	this.hidePage=function() {
		for(var i=this.pageIndex*this.maxTownsInList;i<(this.pageIndex+1)*this.maxTownsInList&&i<this.towns.length&&i>=0;i++){
			this.towns[i].div.style.display="none";
		}
		ev.log.info("on est ds hidePage");
	};
	
	/**
	* Cette méthode est invoquée lorsque l'on tape sur la touche flêche vers le haut du clavier, elle déplace alors la sélection vers le haut. Cela 
	* est réalisé en déselectionnant la précédente sélection, puis en modifiant le numéro de la ligne sélectionnée, puis en sélectionnant la donnée
	* correspondant à cette ligne.
	**/
	this.decreaseSelectedProposition=function() {
		this.deselectProposition();
		this.lineSelected--;
		if(this.lineSelected<0){
			this.lineSelected=this.lines.length-1;
		}
		this.selectProposition();
	};

	/**
	* Cette méthode est invoquée lorsque l'on tape sur la touche flêche vers le bas du clavier, elle déplace alors la sélection vers le bas. Cela 
	* est réalisé en déselectionnant la précédente sélection, puis en modifiant le numéro de la ligne sélectionnée, puis en sélectionnant la donnée
	* correspondant à cette ligne.
	**/
	this.increaseSelectedProposition=function() {
		this.deselectProposition();
		this.lineSelected++;
		if(this.lineSelected>=this.lines.length){
			if(this.lines.length>0){
				this.lineSelected=0;
			}
			else{
				this.lineSelected=-1;
			}
		}
		this.selectProposition();
	};

	/**
	* Sélectionne la ligne donnée 'line'
	**/
	this.selectLine=function(line) {
		this.deselectProposition();
		this.lineSelected=line;
		if(this.lineSelected>=this.lines.length){
			if(this.lines.length>0){
				this.lineSelected=0;
			}
			else{
				this.lineSelected=-1;
			}
		}
		else if(this.lineSelected<0){
			this.lineSelected=this.lines.length-1;
		}
		this.selectProposition();
	};

	/**
	* Sélectionne la donnée désignée par la propriété this.lineSelected
	**/
	this.selectProposition=function() {
		// Le test préliminaire est obligatoire au cas où aucune liste de propostions n'ait été chargée, et par voie de conséquence où this.lines soit
		// vide
		if(this.lines[this.lineSelected]){
			addClass(this.lines[this.lineSelected].div,"selected");
			if(this.lines[this.lineSelected] instanceof PropositionMEVVille){
				this.townSelected=this.lines[this.lineSelected].index;
			}
		}
	};

	/**
	* Désélectionne la donnée désignée par la propriété this.lineSelected
	**/
	this.deselectProposition=function() {
		// Le test préliminaire est obligatoire au cas où aucune liste de propostions n'ait été chargée, et par voie de conséquence où this.lines soit
		// vide
		if(this.lines[this.lineSelected]){
			removeClass(this.lines[this.lineSelected].div,"selected");
		}
	};

	/**
	 * Permet de redonner le focus au champ correspondant à l'objet
	 * completion donné.
	 * @param {Object} _cplObj l'objet completion concerné
	 * @param {Object} _line l'indice de la ligne validée
	 */
	function onClickFocusAgain(_cplObj, _line){
		function unlock(){
			lock=!1;
			validatingLine=!1;
			ev.log.warn('unlocked!');
		}
		function doFocus(){
			ev.log.warn('try to focus...');
			lock=!0;
			_cplObj.elementText.focus();
			// on fixe à nouveau les champs texte après le focus
			// pour que le curseur soit à la bonne place
			_cplObj.elementText.value=_cplObj.lines[_line].nom;
			_cplObj.elementData.value=_cplObj.lines[_line].data;
			setTimeout(unlock, 200);
//			unlock();
		}
		setTimeout(doFocus, 200);
	}

	var validatingLine=!1;
	/**
	* Cette méthode est invoquée par l'appui sur la touche ENTER si on a sélectionné une proposition, dans ce cas on valide au clavier la sélection en
	* cours.
	* @param line l'indice de la ligne validée
	* @param isClick [optionel] 'true' si on veut préciser qu'il s'agit d'un clic
	* 		(le curseur retournera alors vers le champ concerné) 
	**/
	this.validLine=function(line, isClick){
		if(validatingLine){return;}
		validatingLine=!0;									
		if(townRequest) {										
			setLineData(This.elementText,this.towns,this.lines[line]);								
		} 							 	
		// Par défaut, les villes ne sont pas visibles car il faut tester leur page d'appartenance
		this.hidePropositions();
		this.elementText.value=this.lines[line].nom;
		this.elementData.value=this.lines[line].data;
		if(isClick){
			onClickFocusAgain(this, line);
		}
		else{
			validatingLine=!1;
		}				
	};
			
}



/**
* Objet définissant une proposition ville. Cet objet contient essentiellement le nom de la ville et le pays pour affichage et matching, les synonymes
* qui lui sont éventuellement associés (keywords) et le iata qui lui est associé. A l'instanciation, l'objet construit le div qui permettra son 
* affichage.
* - nom: nom de la ville
* - pays: nom du pays
* - code: iata de la ville
* - id: identifiant unique numérique de la ville (peut être 'undefined' car l'ancienne completion doit rester compatible pendant la migration)
* 
* Une propriété 'data' est créée à partir des infos disponibles :
*  - dans le meilleur des cas l'identifiant, le code AITA et le nom de la ville sont insérés dedans (ex.: "v:5580|c:PAR|t:Paris")
*  - si une ou plusieurs donnée(s) manque(nt), on utilise dans l'ordre (le premier valide) :
*    - l'identifiant de la ville
*    - le code AITA de la ville
*    - le texte (nom de la ville)
* REMARQUE : on part du principe de le 'nom' de la ville existe toujours
* 
* La propriété keywords est directement affectée s'il y a lieu. Pour le cas où aucun keyword ne serait affecté, par defaut keywords est un  tableau 
* vide.
* La propriété div est caluclée à la construction.
**/
function PropositionMEVVille(nom,pays,code,id){
	this.nom=nom;
	this.code=code;
	this.id=id;
	if(this.id&&this.code&&this.code!="null"){
		this.data="v:"+this.id+"|c:"+this.code+"|t:"+this.nom;
	}
	else if(this.id){
		this.data="v:"+this.id;
	}
	else if(this.code&&this.code!="null"){
		this.data="c:"+this.code;
	}
	else{
		this.data=this.nom;
	}
	this.pays=pays;
	
	// Calcul du div
	this.div=document.createElement("DIV");
	this.div.className="propositionVille";
	this.div.appendChild(document.createTextNode(this.nom+", "+this.pays));
	this.div.style.display="none";
		
	this.index=-1;
	
	// Tableaux d'aéroports et mots-clés
	this.aeroports=[];
	this.keywords=[];
}
PropositionMEVVille.prototype={
	/**
	 * Méthode permettant d'ajouter un mot-clé
	 * dans la liste de cette ville.
	 * @param {Object} kw le mot-clé à ajouter
	 */
	add: function(kw){
		this.keywords.push(kw);
	},
	/**
	 * Méthode permettant d'ajouter un aéroport
	 * dans la liste de cette ville.
	 * @param {Object} a l'aéroport à ajouter
	 */
	addAeroport: function(a){
		this.aeroports.push(a);
	},
	/**
	* Méthode déterminant si la proposition ville correspond à l'expression régulière passée en paramètre, i.e. l'une de ses données (nom, pays, code, 
	* ou keyword) débute par le texte value. Retourne un booléen.
	* @param {RegExp} _re expression à comparer aux nom, pays, code et mots-clés
	**/
	matches: function(_re){
		if(textMatch(this.nom,_re)){return true;}
		if(textMatch(this.pays,_re)){return true;}
		if(textMatch(this.code,_re)){return true;}
		for(var i=this.keywords.length;i--;){
			if(textMatch(this.keywords[i],_re)){return true;}
		}
		return false;
	},

	retournMatch: function(_re){
		for(var i=this.keywords.length;i--;){
			if(textMatch(this.keywords[i],_re)){
				return this.keywords[i];
			}
		}
		return null;
	}

};

/**
* Objet définissant une proposition aéroport. Cet objet contient essentiellement le nom de l'aéroport pour affichage et matching, les synonymes
* qui lui sont éventuellement associés (keywords) et le iata qui lui est associé. A l'instanciation, l'objet construit le div qui permettra son 
* affichage.
* - nom: nom de l'aéroport
* - code: iata de l'aéroport
* - id: identifiant unique numérique de l'aéroport (peut être 'undefined' car l'ancienne completion doit rester compatible pendant la migration)
*
* Une propriété 'data' est créée à partir des infos disponibles :
*  - dans le meilleur des cas l'identifiant, le code AITA et le nom de l'aéroport sont insérés dedans (ex.: "a:5829|c:CDG|t:Ch. De Gaulle")
*  - si une ou plusieurs donnée(s) manque(nt), on utilise dans l'ordre (le premier valide) :
*    - l'identifiant de l'aéroport
*    - le code AITA de l'aéroport
*    - le texte (nom de l'aéroport)
* REMARQUE : on part du principe de le 'nom' de l'aéroport existe toujours
*
* La propriété keywords est directement affectée s'il y a lieu. Pour le cas où aucun keyword ne serait affecté, par defaut keywords est un  tableau 
* vide.
* La propriété div est caluclée à la construction.
**/
function PropositionMEVAeroport(nom,code,id) {
	this.nom=nom;
	this.code=code;
	this.id=id;
	if(this.id&&this.code&&this.code!="null"){
		this.data="a:"+this.id+"|c:"+this.code+"|t:"+this.nom;
	}
	else{
		if(this.id){
			this.data="a:"+this.id;
		}
		else if(this.code&&this.code!="null"){
			this.data="c:"+this.code;
		}
		else{
			this.data=this.nom;
		}
	}
	this.div=null;

	// Calcul du div
	this.div=document.createElement("DIV");
	this.div.className="propositionAeroport";
	this.div.appendChild(document.createTextNode(nom));
	
	// Tableau de mots-clés
	this.keywords=[];
}
PropositionMEVAeroport.prototype={
	/**
	 * Méthode permettant d'ajouter un mot-clé
	 * dans la liste de cet aéroport.
	 * @param {Object} kw le mot-clé à ajouter
	 */
	add: function(kw){
		this.keywords.push(kw);
	},
	/**
	* Méthode déterminant si la proposition aéroport correspond à l'expression régulière passée en paramètre, i.e. l'une de ses données (nom, code, 
	* ou keyword) débute par le texte value. Retourne un booléen.
	* @param {RegExp} _re expression à comparer aux nom, code et mots-clés
	**/
	matches: function(_re){
		if(textMatch(this.nom,_re)){return true;}
		if(textMatch(this.code,_re)){return true;}
		for(var i=0;i<this.keywords.length;i++){
			if(textMatch(this.keywords[i],_re)){return true;}
		}
		return false;
	}
};

/**
* Objet qui référence toutes les intances de CompletionMEV présentes sur la page
**/
var completionMEVPool={
	pool: [],
	add: function(completionMEV){
		this.pool.push(completionMEV);
		return this.pool.length-1;
	},
	get: function(poolIndex){
		return this.pool[poolIndex];
	}
};
