

/**
 * (c) Copyright 2010 Easyvoyage S.A.
 *
 * Module de base, contenant les éléments nécessaires
 * à tous les Javascript d'Easyvoyage.
 *
 * Fonction anonyme de déclaration de classe(s) utiles.
 * Certaines classes/fonctions ne sont visible que par
 * la(es) classe(s) concernée(s) (déclarée(s) ici).
 * Ce mécanisme de fonction anonyme permet de reproduire
 * un système d'encapsulation digne d'un langage de
 * programmation évolué (comme le Java).
 *
 * Cette fonction est exécutée en fin de fichier.
 *
 * @this {Window} le contexte est l'objet window de la page
 */
(function() {
	//'use strict';
	// Raccourci vers l'objet window de la page
	var window = this,
			// raccourci vers location
			location = window.location,
			// host sans le port (Domain Name)
			DN = location.host.replace(/:.+$/, ''),
			// Sert seulement à représenter le mot-clé 'undefined'
			// (son nom sera raccourci par les optimisations automatiques ; bien plus court que 'undefined')
			UNDEFINED, NULL = null,
			// raccourci vers le "core object" et son nom
			CORE = {}, CORE_MODULE_NAME = 'core', CORE_PFX = CORE_MODULE_NAME + '#', INIT_METHOD_NAME = '<init>',
			// Raccourcis vers les datas en provenance du contexte Java
			DATA,
			// Raccourcis vers la variable globale contenant la langue d'affichage : 'ev.lang'
			LANG,
			// Raccourcis vers la variable globale contenant les chemins généraux des contenus statiques : 'ev.path'
			PATH,
			// Constantes représentant les types 'fonction', 'string' et 'number'
			TYPE_FUNCTION = 'function', TYPE_STRING = 'string', TYPE_NUMBER = 'number',
			// Constante représentant la propriété console de 'window'
			CONSOLE = 'console',
			// raccourcis vers des méthodes utiles
			delay = window.setTimeout,
			// constantes contenant les noms des propriétés de 'arguments' : 'callee' et 'caller'
			CALLEE = 'callee', CALLER = 'caller',
			// synonymes de true et false
			FALSE = !window, TRUE = !FALSE,
			// Niveaux de logs existants
			LEVELS = ['NONE', 'FATAL', 'ERROR', 'WARN', 'INFO', 'DEBUG', 'LOG'],
			// Méthodes de logs existantes (selon le niveau)
			METHODS = [UNDEFINED, LEVELS[2], LEVELS[2], LEVELS[3], LEVELS[4], LEVELS[6], LEVELS[6]],
			// raccourcis vers termes utiles
			HTTP_SCHEME = 'http://', EASYVOYAGE_DOMAIN_SFX = '.easyvoyage.com',
			// raccourci vers fonctions DOM utiles
			ELTS_BY_TAGNAME = 'getElementsByTagName', CREATE_ELEMENT = 'createElement', CREATE_ELEMENT_NS = 'createElementNS', W3_XHTML = HTTP_SCHEME + 'www.w3.org/1999/xhtml',
			// Variable contenant les configurations generales possibles (ancre de l'url)
			CONF = {
				verbose: 2,
				optim: TRUE
			},
			// Variable contenant le système de logs
			LOG = {
				/**
		 * Pour activer mode DEBUG ajouter 'verbose:3', 'verbose:4'
		 * ou 'verbose:5' au début de l'ancre (ex: #verbose:4 pour
		 * activer le mode INFO).
		 *
		 * @type {number} niveau de logs par défaut.
		 */
				level: 2
			},
			// Raccourcis vers les méthodes du système de logs
			fatalMsg, errorMsg, warnMsg, infoMsg, debugMsg, // Timestamp du début
			ts_start,
			// Some errors' text
			ERR_IS_NOT_VALID = 'is not valid', ERR_ANOTHER_EXISTS = 'Another method or property already exists with that name.',
			// Raccourci vers l'user agent FIXME remettre ici le detail complet sur le navigateur (a voir)
			userAgent = window.navigator.userAgent.toLowerCase();

	// *************************************************
	// ******************** Util ***********************
	// *************************************************

	//  window.Error.prototype.toString = function() {
	//    //return this.message || this.description || 'No message found :( !';
	//    var text = '{\n', property;
	//    for (property in this) {
	//      text += property + ': ' + this[property] + ',\n';
	//    }
	//    text += 'constructor: ' + this.constructor + '\n';
	//    return text + '}';
	//  };

	function currentTimeMillis() {
		return new Date().getTime();
	}
	// on stocke le timestamp du début
	ts_start = currentTimeMillis();

	/**
	 * Permet de savoir si l'objet donné est une fonction ou pas.
	 *
	 * @param {*} func objet qui doit être comparé à une fonction.
	 * @return {boolean} true si l'objet est une fonction, sinon false.
	 */
	function isFunction(func) {
		return typeof(func) === TYPE_FUNCTION;
	}

	/**
	 * Fonction opposée à isFunction().
	 *
	 * @param {*} func objet qui doit être comparé à une fonction.
	 * @return {boolean} true si l'objet n'est pas une fonction, sinon false.
	 */
	function isNotFunction(func) {
		return !isFunction(func);
	}

	/**
	 * Permet de savoir si l'objet donné est une string ou pas.
	 *
	 * @param {*} str objet qui doit être comparé à une string.
	 * @return {boolean} true si l'objet est une string, sinon false.
	 */
	function isString(str) {
		return typeof(str) === TYPE_STRING;
	}

	/**
	 * Permet de savoir si l'objet donné est un nombre ou pas.
	 *
	 * @param {*} n objet qui doit être comparé à un nombre.
	 * @return {boolean} true si l'objet est un nombre, sinon false.
	 */
	function isNumber(n) {
		return typeof(n) === TYPE_NUMBER;
	}

	/**
	 * Permet de savoir si l'objet donné est un tableau ou pas.
	 *
	 * @param {*} array objet qui doit être comparé à un tableau.
	 * @return {boolean} true si l'objet est un tableau, sinon false.
	 */
	function isArray(array) {
		return !!array && array.constructor === Array;
	}

	/**
	 * Fonction de reconnaissance d'IE en attendant de rappatrier
	 * les éléments de création de genericNavigator.
	 *
	 * @return {!number} un nombre correspondant au numéro de version si le navigateur est IE, sinon 0.
	 */
	function isIE() {
		var idx = userAgent.indexOf('msie');
		if (idx < 0) {
			return 0;
		}
		return userAgent.substring(idx + 4).match(/[0-9]+\.[0-9]+/);
	}

	/**
	 * Affiche le message donné dans le niveau de logs
	 * choisi.
	 *
	 * @param {!number} level Niveau de logs à utiliser.
	 * @param {!string} message Message à afficher.
	 */
	function logByLevel(level, message) {
		// méthode de logs correspondant au niveau de logs demandé
		var meth = METHODS[level],
				// console qui sera utilisée
				console = window[CONSOLE];
		//    console.log('logging message '+message+' with level '+level+'... [meth: '+meth+', console: '+console+']');
		meth = (meth && meth.toLowerCase());
		// On teste le niveau de logs, ainsi que l'existance et "l'exécutabilité" de la méthode
		if (meth && console && ((isIE() && console[meth]) || isFunction(console[meth]))) {
			// affichage du message
			//      console[meth](message);
			console[meth](LEVELS[level] + ' - ' + (currentTimeMillis() - ts_start) + ' - ' + message);
		}
	}

	// Si ev existe déjà, on sort
	if (window.ev && window.ev.core) {
		// on utilise le niveau de logs actif le plus bas pour écrire ça
		logByLevel(Math.min(3, LOG.level), CORE_PFX + INIT_METHOD_NAME + ': déjà défini avant!');
		return;
	}

	// 'window.EV=' pour compatibilité ascendante (namespace EV) FIXME [ygally] à virer. Utiliser "window.ev"
	// On crée une première version vide de ev. (cela évite de rentrer 2 fois en même temps dans la partie suivante)
	window.EV = (window.ev = {core: CORE});
	//  logByLevel(LOG.level, CORE_PFX + INIT_METHOD_NAME + ': Easy core en cours de définition!');


	// *************************************************
	// **************** Util (Suite) *******************
	// *************************************************
	/**
	 * Initialisation du système de logs.
	 */
	(function() {
		function stacktrace() {
			var caller = arguments[CALLEE][CALLER] && arguments[CALLEE][CALLER]['arguments'][CALLEE][CALLER], stackText = '', cnt = -1, a, l, i;
			if (caller) {
				stackText += '\n - Stack Trace: ';
				while (caller) {
					++cnt;
					stackText += '\n ' + cnt + ':' + caller.name + '(';
					a = caller['arguments'];
					l = a.length;
					if (l > 0) {
						stackText += (a[0].constructor && a[0].constructor.name) || '?';
						for (i = 1; i < l; ++i) {
							stackText += ',' + ((a[i].constructor && a[i].constructor.name) || '?');
						}
					}
					stackText += ')';
					caller = a[CALLEE][CALLER];
				}
			}
			return stackText;
		}

		/**
		 * Crée une fonction qui prendra en charge les logs
		 * du niveau donné.
		 *
		 * @param {number} level niveau de logs de la fonction à créer.
		 * @return {Function} la fonction qui va gérer ce niveau de logs.
		 */
		function createLogHandler(level) {
			/**
			 * Permet de logger un message du bon niveau
			 * de log.
			 *
			 * @param {!(string|Array.<string>)} message message à logger (ou tableau de message à logger).
			 * @param {boolean=} stack permet de préciser si on veut les infos de la pile d'appel.
			 */
			return function(message, stack) {
				//window.console.log('logging message '+message+' with level '+level+'...');
				if (LOG.level >= level) {
					//window.console.log('logging message '+message+' with level '+level+'... LEVEL OK');
					if (typeof message === 'array') {
						logByLevel(level, message.join(' '));
					}
					else {
						logByLevel(level, message + (stack ? stacktrace() : ''));
					}
					//window.console.log('logging message '+message+' with level '+level+'... DONE!');
				}
				//else { window.console.warn('logging message '+message+' with level '+level+'... LEVEL PAS OK [>'+LOG.level+']'); }
			};
		}

		/**
		 * Phase de définition réelle du système.
		 */
		(function() {
			var n = LEVELS.length, level;
			while (n) {
				--n;
				level = LEVELS[n];
				LOG[level] = n;
				if (METHODS[n]) {
					LOG[level.toLowerCase()] = createLogHandler(n);
				}
			}
		}()); // Exécution de la définition du système
	}()); // Exécution de l'initialisation du système de logs

	// initialisation des raccourcis vers les méthodes de ev.log
	fatalMsg = LOG.fatal;
	errorMsg = LOG.error;
	warnMsg = LOG.warn;
	infoMsg = LOG.info;
	debugMsg = LOG.debug;
	// Expression régulière constante représentant un séparateur de paramètres.
	// Permet de "spliter" une chaine contenant plusiseurs paramètres (dans une URL)
	var RE_PARAM_SEPARATOR = /&/,
			RE_JS_OPTIMIZED = /(\/js)\/optim\/.*$/,
			createSimpleMap = (function() {
				/**
				 * Méthode utile d'ajout d'une entrée dans une
				 * map d'éléments 'property=value'.
				 * @param {Object} m map dans laquelle sera ajouté le paramètre.
				 * @param {String} a séparateur d'affectation (ex: '=' dans 'property=value').
				 * @param {Object} p entrée de type 'property=value'.
				 */
				function put(m, a, p) {
					if (!p.length) {
						warnMsg('core#_createSimpleMap()._put(): un param vide (2 séparateurs de paramètres côte à côte, ou séparateur en début/fin de chaîne)');
						return;
					}
					//    debugMsg('core#_createSimpleMap()._put(Map, Param): inserting paramètre "'+p+'"...');
					var nv = p.split(new RegExp(a)), l = nv.length;
					--l;
					if (l) {
						if (!nv[0].length) {
							errorMsg('core#_createSimpleMap()._put(Map, Param): paramètre "' + p + '" mal renseigné [manque le nom du paramètre]');
							return;
						}
						if (m[nv[0]]) {
							warnMsg('core#_createSimpleMap()._put(Map, Param): paramètre "' + nv[0] + '" renseigné plusieurs fois (on utilise la dernière valeur donnée "' + m[nv[0]] + '")');
							return;
						}
						--l;
						while (l) {
							// si plusieurs séparateurs d'affectation, on considère le premier comme séparateur, et les suivants sont compris dans la valeur
							nv[l] += a + nv[l + 1];
							--l;
						}
						m[nv[0]] = nv[1];
						return;
					}
					warnMsg('core#_createSimpleMap()._put(Map, Param): paramètre "' + p + '" mal renseigné [manque caractère \'' + a + '\']');
					m[p] = '';
				}

				/**
		 * Méthode permettant de construire une map avec tous les
		 * paramètres du tableau donné.
		 *
		 * @param {String} a séparateur d'affectation (ex: '=' dans 'property=value').
		 * @param {Object} ps tableau de paramètres à ajouter.
		 * @return {Object } la map ainsi créée.
		 */
				return function(a, ps) {
					if (!ps || !ps.length) {
						infoMsg('Rien trouvé');
						return {};
					}
					//    debugMsg('core#_createSimpleMap(): Parameters array ['+ps+']');
					var m = {}, n = ps.length;
					while (n) {
						--n;
						put(m, a, ps[n]);
					}
					return m;
				};
			}()),

			// Gestion de paramètres d'URL.
			// Le résultat de cette fonction anonyme est stocké dans une variable temporaire qui sera reportée plus bas dans 'ev.tools'.
			paramTools = (function() {
				// Expression régulière permettant de trouver le début des paramètres.
				var RE_PARAMS = /^[^#]*\?([^#]*)(#|$)/,
						// Expression régulière constante représentant un séparateur de paramètres situé dans une ancre.
						// Permet de "spliter" une chaine contenant plusiseurs paramètres séparés par des '/'.
						RE_HASH_PARAM_SEPARATOR = /\//,
						// Expression régulière représentant la fin d'une URL (à partir de l'ancre s'il y en a une).
						RE_HASH_PARAMS = /(#([^\/]*(\/([^\/]*))*)?)?$/,
						// Expression régulière représentant l'ancre entière.
						RE_ANCHOR = /(#(.*))?$/,
						// Expression régulière représentant des doubles slash ('//') dans l'ancre.
						RE_ANCHOR_SLASHES = /(#.*)\/\/+/g;

				function getAnchorParameters(u) {
					if (!u || !RE_HASH_PARAMS.test(u)) {
						warnMsg('tools#getAnchorParameters()> URL impossible à lire (' + u + ')');
						return {};
					}
					var params = RegExp.$2.replace(/^\/+/, '').replace(/\/+$/, '');
					if (!params) {
						// en cas de chaine vide, on renvoi une map vide
						return {};
					}
					//    debugMsg('ev.tools.params#_getAnchorParameters(): Found parameters in anchor : "'+u+'"\n    => "'+params+'"');
					return createSimpleMap(':', params.split(RE_HASH_PARAM_SEPARATOR));
				}

				// vérification de l'ancre de la page pour gérer le niveau de verbosité et le flag d'optim
				var anchorParams = getAnchorParameters(location.hash);
				if (anchorParams.verbose && (/^([345])$/).test(anchorParams.verbose)) {
					CONF.verbose = LOG.level = parseInt(RegExp.$1, 10);
					fatalMsg('VERBOSITY has been set to "' + CONF.verbose + '" !');
				}
				if (anchorParams.optim && (/^(0|off|false|no|non)$/i).test(anchorParams.optim)) {
					CONF.optim = FALSE;
					fatalMsg('OPTIMISATIONS has been set to "' + CONF.optim + '" !');
				}
				if (anchorParams.era && (/^(test|production)$/i).test(anchorParams.era)) {
					CONF.era = RegExp.$1;
					fatalMsg('ERA has been set to "' + CONF.era + '" !');
				}

				return {
					/**
					 * Fonction permettant de récupérer les paramètres
					 * fournis dans l'URL donnée (ou l'URL de la page si
					 * rien n'est précisé).<br>
					 * NOTA : cette méthode ne gère pas de cache, à chaque appel
					 * elle refait le traitement.
					 *
					 * @param {String} u [optionel] url à lire [par défaut: document.location.href].
					 */
					getUrlParameters: function(u) {
						u = (u || location.search);
						u = u && RE_PARAMS.test(u) && RegExp.$1;
						if (!u) {return {};}
						return createSimpleMap('=', u.split(RE_PARAM_SEPARATOR));
					},

					/**
					 * Fonction permettant de récupérer les paramètres fournis
					 * dans l'ancre d'une URL.<br>
					 *
					 * @param {String} u URL dans laquelle on doit récupérer les paramètres passés dans l'ancre.
					 */
					getAnchorParameters: getAnchorParameters,

					/**
					 * Méthode d'ajout / mise à jour d'un paramètre
					 * de l'ancre.
					 * @param {Object} url URL à modifier.
					 * @param {Object} name nom du paramètre à ajouter/changer.
					 * @param {Object} value valeur du paramètre à ajouter/changer.
					 */
					setParamInAnchor: function(url, name, value) {
						//    debugMsg('tools#setParamInAnchor()> Modification de l\'ancre dans l\'URL... ('+name+'='+value+')');
						var oldValue = getAnchorParameters(url)[name];
						if (oldValue) {
							if (oldValue === value) {
								// Si l'ancienne valeur du paramètre est égale à la nouvelle, on ne change rien et on sort
								//        debugMsg('tools#setParamInAnchor()> L\'ancre est déjà à jour pour le paramètre. ('+name+'='+oldValue+')');
								return url;
							}
							//      debugMsg('tools#setParamInAnchor()> Paramètre \''+name+'\' trouvé dans l\'ancre: '+oldValue+' [on l\'enlève]');
							// si le paramètre était présent dans l'url, on l'enlève
							url = url.replace(new RegExp('(#.*\\/)(' + name + ':)[^\\/]*\\/', 'g'), '$1');
						}
						if (value || value === 0) {
							// Ajout du nouveau paramètre dans l'ancre et suppression des doubles slashs ('//')
							url = url.replace(RE_ANCHOR, '#$2/' + name + ':' + value + '/').replace(RE_ANCHOR_SLASHES, '$1/');
						}
						//    debugMsg('tools#setParamInAnchor()> Paramètre \''+name+'\' modifié dans l\'ancre de l\'URL : '+url);
						return url;
					}
				};
			}()); // exécution de la fonction anonyme ici

	/**
	 * Fonction utilitaire permettant d'ajouter une méthode de délégation
	 * vers la fonction donnée sur un objet.
	 *
	 * @param {!Object} object objet dans lequel on va ajouter la méthode.
	 * @param {!string} name nom de la méthode de délégation à ajouter.
	 * @param {!Function} func la fonction à laquelle la délégation se réfère.
	 * @return {!boolean} true si la méthode a bien été ajoutée, sinon false.
	 */
	function addDelegate(object, name, func) {
		// si les propriétés de l'objet sont celles attendues alors
		// on fixe la fonction donnée, dans la propriété, de l'objet source donné
		if (!object || !name || isNotFunction(func)) {
			return FALSE;
		}
		object[name] = function() {
			return func.apply(object, arguments);
		};
		return TRUE;
	}

	// *************************************************
	// *************** Datas from Java *****************
	// *************************************************
	DATA = window.evData || {};
	LANG = DATA.lang || window.lang || 'fr_FR'; // Par sécutité, si aucune variable 'window.evData.lang' ou 'window.lang' n'est définie : fr_FR
	LANG = {
		current: LANG,
		language: LANG.replace(/_.*$/, ''),
		country: LANG.replace(/^.*_/, '')
	};
	window.lang = LANG.current; // Et du coup, par sécurité on le remet dans 'window.lang' (FIXME en attendant de n'utiliser plus que ev.lang.current)
	PATH = (DATA.path || window.path || {
		cdn: HTTP_SCHEME + 'cdn' + EASYVOYAGE_DOMAIN_SFX,
		img: HTTP_SCHEME + 'img' + EASYVOYAGE_DOMAIN_SFX,
		js: HTTP_SCHEME + 'cdn' + EASYVOYAGE_DOMAIN_SFX + '/js',
		css: HTTP_SCHEME + 'cdn' + EASYVOYAGE_DOMAIN_SFX + '/styles',
		era: HTTP_SCHEME + 'era' + EASYVOYAGE_DOMAIN_SFX
	}); // Par sécutité, si aucune variable 'window.path' n'est définie : on prend les chemins de Production

	// en cas de configuration OPTIM donnée par l'URL
	if (!CONF.optim) {
		// désoptimisation du path.js o_O
		PATH.js = PATH.js.replace(RE_JS_OPTIMIZED, '$1');
	}

	// en cas de configuration ERA donnée par l'URL
	if (CONF.era) {
		switch (CONF.era) {
			case 'production': // sur la production
				PATH.era = HTTP_SCHEME + 'era' + EASYVOYAGE_DOMAIN_SFX;
				break;
			case 'test': // sur le serveur de pré-production
				PATH.era = HTTP_SCHEME + 'test.era.lcom';
				break;
			default:
				// sinon on ne fait rien
				break;
		}
	}

	// garbage collect
	// FIXME [ygally] à faire dés que possible. Pour le moment ces variable sont lue à cet endroit précis.
	//  window.lang=UNDEFINED;
	//  window.path=UNDEFINED;
	//  delete window.lang;
	//  delete window.path;
	//  if (!LANG.current) {
	//    throw CORE_PFX + INIT_METHOD_NAME + ': langue de la page non définie. [window.lang]';
	//  }
	//  if (!PATH) {
	//    throw CORE_PFX + INIT_METHOD_NAME + ': les chemins généraux (vers contenus statiques) non définis. [window.path]';
	//  }


	// *************************************************
	// ************* Extensions des String *************
	// *************************************************

	if (!String.prototype.trim) {
		/**
		 * Patch pour la classe String.
		 * Supprime les espaces inutiles en début et fin
		 * de la chaîne.
		 */
		String.prototype.trim = function() {
			return this.replace(/(^\s+|\s+$)/g, '');
		};
	}
	if (!String.prototype.capitalize) {
		/**
		 * Patch pour la classe String.
		 * Transforme la première lettre de la chaine
		 * en une majuscule.
		 *
		 * NOTA : seulement si non définie avant
		 */
		String.prototype.capitalize = function() {
			return this.charAt(0).toUpperCase() + this.substring(1);
		};
	}


	// *************************************************
	// *************** Listening support ***************
	// *************************************************

	var ADD_LISTENER_METHOD_NAME = 'addListener',
			FIRE_METHOD_NAME = 'fireEvent';

	/**
	 * Fonction privée gérant l'ajout d'un nouveau type d'événement.<br>
	 * Cette fonction ajoute un nouveau tableau de listeners dans la map donnée.
	 * Le nouveau tableau de listeners est ensuite retourné.
	 *
	 * @param {Object} provider : objet maitre de l'objet courant (qui est seul à pouvoir envoyer des événements sur l'objet).
	 * @param {Object} eventMap : map de tous les événements gérés sur cet objet.
	 * @param {string} eventType : nouveau type d'événement à rajouter.
	 */
	function createEventTypeListeners(provider, eventMap, eventType) {
		// Création du tableau de listeners pour le type d'événement donné.
		var listeners = (eventMap[eventType] = []);

		/**
		 * Méthode de propagation d'un événement.
		 *
		 * @param {!Object} supportObject objet ayant le support d'événements.
		 * @param {!Object} source : objet à l'initiative de l'événement (doit être l'objet provider ; aucun autre objet ne peut provoquer d'événement).
		 * @param {?Object=} data : un objet qui peut contenir toutes sortes d'infos transmises par l'objet source.
		 */
		function fire(supportObject, source, data) {
			if (source === provider) {
				var i;
				// NB : ici on ne stocke pas la taille du tableau avant, car elle peut changer pendant les itérations de la boucle
				for (i = 0; i < listeners.length; ++i) {
					try {
						// chaque listener est appelé dans un bloc try...catch pour éviter de planter
						// le process principal dans un des listener.
						listeners[i].call(supportObject, source, eventType, data);
					}
					catch (exception) {
						errorMsg(CORE_PFX + 'fire(' + source + '): Exception = ' + (exception && exception.message || 'No message found in Error object [' + exception + '] !'));
					}
				}
			}
			else {
				errorMsg(CORE_PFX + 'fire(' + source + "): Error = La source de l'événement n'est pas le Provider de l'objet support [" + provider + ']');
			}
		}

		// On stocke la fonction de propagation sur le tableau de listeners
		listeners.fire = fire;

		// On retourne le tableau de listeners créé
		return listeners;
	}

	/**
	 * Permet d'ajouter un listener à l'objet du contexte courant pour
	 * un type d'événement donné.<br>
	 * <br>
	 * NOTA : contexte 'this' = objet courant du support d'événements.
	 *
	 * @param {!Object} provider objet maitre de l'objet courant (qui est seul à pouvoir envoyer des événements sur l'objet).
	 * @param {!Object} eventMap map de tous les événements gérés sur cet objet.
	 * @param {!string} eventType type d'événement concernant le listener.
	 * @param {!Function} listener listener à ajouter à la liste du type d'événement donné.
	 */
	function addListener(provider, eventMap, eventType, listener) {
		// si le type n'est pas une chaine de caractères, on sort
		if (!isString(eventType)) {
			errorMsg(CORE_PFX + '_addListener(): eventType must be a string!');
			return;
		}

		// si le listener n'est pas une fonction, on ne l'ajoute pas
		if (isNotFunction(listener)) {
			errorMsg(CORE_PFX + '_addListener(): listener must be a function!');
			return;
		}

		// Récupération du tableau de listener pour le le type d'événement donné.
		// Ou création de ce dernier s'il n'existe pas
		var listeners = (eventMap[eventType] || createEventTypeListeners(provider, eventMap, eventType));

		// Ajout du listener donné à la fin du tableau
		listeners.push(listener);
	}

	/**
	 * Permet de générer un événement sur l'objet du support
	 * pour un type d'événement donné.
	 *
	 * @param {!Object} supportObject objet ayant le support d'événements.
	 * @param {!Object} source initiateur de l'événement.
	 * @param {!Object} eventMap map de tous les événements gérés sur cet objet.
	 * @param {!string} eventType type d'événement à générer.
	 * @param {?*=} data un objet qui peut contenir toutes sortes d'infos transmises par l'objet source.
	 */
	function fireEvent(supportObject, source, eventMap, eventType, data) {
		// si le type n'est pas une chaine de caractères, on sort
		if (!isString(eventType)) {
			// FIXME factoriser les erreurs
			errorMsg(CORE_PFX + '_fireEvent(): eventType must be a string!');
			return;
		}

		// s'il n'y a pas de listener pour le type donné, on sort
		if (!eventMap[eventType]) {
			// FIXME factoriser les erreurs
			infoMsg(CORE_PFX + '_fireEvent(): no listener for this eventType! [src:' + source + ',typ:' + eventType + ',dt:' + data + ']');
			return;
		}

		// propagation de l'événement
		eventMap[eventType].fire(supportObject, source, data);
	}

	/**
	 * addLS (add Listening Support).<br>
	 * <br>
	 * Fonction permettant d'attribuer un système d'écoute d'événements
	 * à n'importe quel objet.
	 *
	 * @param {!Object} object objet qui doit supporter l'écoute d'événements.
	 * @param {!Object} provider objet qui est seul à pouvoir envoyer des événements sur l'objet.
	 * @return {boolean} <code>true</code> si tout s'est bien passé, <code>false</code> sinon.
	 */
	function addListeningSupport(object, provider) {
		if (!object || !provider) {
			errorMsg(CORE_PFX + '_addListeningSupport(' + object + ', ' + provider + "): l'objet support et le Provider ne doivent pas être nuls.");
			return FALSE;
		}
		if (isFunction(object.isTheProvider)) {
			// Si le support a déjà été ajouté ... 2 choix
			if (object.isTheProvider(provider)) {
				// si le provider est celui qui a été utilisé précédemment, on sort et on renvoi ok
				//debugMsg(CORE_PFX + '_addListeningSupport(' + object + ', ' + provider + "): l'objet supporte déjà les événments.");
				return TRUE;
			}
			// sinon, on affiche une erreur, on sort et on renvoi pas ok
			errorMsg(CORE_PFX + '_addListeningSupport(' + object + ', ' + provider + "): l'objet supporte déjà les événments et le Provider est différent.");
			return FALSE;
		}
		// Map de <eventType, listeners> (pour listening support de l'objet donné)
		var eventMap = {};

		if (object[ADD_LISTENER_METHOD_NAME]) {
			errorMsg(CORE_MODULE_NAME + '#_addListeningSupport(): Cannot create method "#' + ADD_LISTENER_METHOD_NAME + '(). ' + ERR_ANOTHER_EXISTS);
			return FALSE;
		}
		if (object[FIRE_METHOD_NAME]) {
			errorMsg(CORE_PFX + '_addListeningSupport(): Cannot create method "#' + FIRE_METHOD_NAME + '(). ' + ERR_ANOTHER_EXISTS);
			return FALSE;
		}

		/**
		 * Permet d'ajouter un listener à l'objet (correspondant au contexte 'this')
		 * pour le type d'événement donné.<br>
		 * <br>
		 * Chaque listener est une fonction à appeler avec en paramètre :
		 * - source : l'objet à l'initiative de l'événement
		 * - eventType : le type d'événement donné
		 * - data : un objet qui peut contenir toutes sortes d'infos transmises par l'objet source
		 * ... et le contexte lors de l'appel de cette fonction est :
		 * - this = l'objet sur lequel l'événement est généré

		 * @see #fireEvent(source, eventType, data)
		 *
		 * @param {!string} eventType nouveau type d'événement à rajouter.
		 * @param {!Function} listener listener à ajouter à la liste du type d'événement donné.
		 */
		object[ADD_LISTENER_METHOD_NAME] = function(eventType, listener) {
			// ajout du listener en utilisant la fonction interne
			addListener(provider, eventMap, eventType, listener);
		};

		/**
		 * Permet de générer un événement sur l'objet donné pour un type d'événement donné.
		 *
		 * @param {!Object} source initiateur de l'événement.
		 * @param {!string} eventType type d'événement à générer.
		 * @param {?Object=} data un objet qui peut contenir toutes sortes d'infos transmises par l'objet source.
		 */
		object[FIRE_METHOD_NAME] = function(source, eventType, data) {
			// génération de l'événement en utilisant la fonction interne
			fireEvent(object, source, eventMap, eventType, data);
		};

		/**
		 * Permet de comparer le provider qui a été utilisé pour ajouter le support d'événements.
		 *
		 * @param {!Object} oneProvider le provider à comparer.
		 * @return {boolean} true si c'est le bon provider (qui est seul à pouvoir envoyer des événements sur l'objet), sinon false.
		 */
		object.isTheProvider = function(oneProvider) {
			return provider === oneProvider;
		};

		// tout s'est bien passé
		return TRUE;
	}

	// *************************************************
	// *********** Simple Listenning Support ***********
	// *************************************************

	/**
	 * Fonction qui construit un wrapper permettant à la fonction
	 * donnée d'avoir moins de paramètres que nécessaire pour le
	 * listener.<br>
	 * La fonction "wrapper" sera une fonction l'englobant qui elle
	 * gérera les paramètres supplémentaires.<br>
	 * <br>
	 * On veut ici ne gérer que les événements de type choisi à
	 * l'avance et l'objet source sera toujours l'objet support
	 * lui-même. Le seul paramètre qui pourra changer d'un événement
	 * à l'autre est 'data'.
	 *
	 * @param {!Object} supportObject objet ayant le support d'événements.
	 * @param {?Function} eventHandler la fonction encapsuler.
	 * @return {?Function} la fonction "wrapper" (ou null si le paramètre donné est <code>null</code> ou n'est pas une fonction).
	 */
	function getWrapperFunction(supportObject, eventHandler) {
		if (isFunction(eventHandler)) {
			return function(source, eventType, data) {
				debugMsg('generic wrapperFunction(' + source + ', ' + eventType + ', ' + data + ') [' + eventHandler.name + ']');
				// on transmet à la fonction interne, seulement le contexte de l'appel et le paramètre 'data'
				eventHandler.call(supportObject, data);
			};
		}
		// si le paramètre eventHandler n'est pas une fonction, on renvoie null
		return NULL;
	}

	/**
	 * Permet d'ajouter à l'objet donné, une gestion simplifiée
	 * d'un type d'événements.
	 *
	 * @param {!Object} object objet sur lequel il faut ajouter le Support d'événement simplifié.
	 * @param {!string} eventType type des événement à gérer.
	 * @param {string=} addListenerMethodName nom de la méthode qui permet d'ajouter un listener [défaut: add + EVENT_TYPE + Listener() ; ex: event 'test' -> méthode 'addTestListener()'].
	 * @param {string=} fireMethodName nom de la méthode qui permet de générer un événement [défaut: fire + EVENT_TYPE + Event() ; ex: event 'test' -> méthode 'fireTestEvent()'].
	 * @param {Object=} provider objet qui est seul à pouvoir envoyer des événements sur l'objet (facultatif ; par défaut c'est l'objet lui-même).
	 * @return {boolean} <code>true</code> si tout s'est bien passé, <code>false</code> sinon.
	 */
	function addSimpleListeningSupport(object, eventType, addListenerMethodName, fireMethodName, provider) {
		//FIXME throw '' si !object ou !eventType ou !eventType.length

		// si le provider n'est pas renseigné on utilisera tout le temps l'objet lui-même
		provider = provider || object;

		// ajout du support d'événements pour l'objet donné
		// l'objet sera son propre provider (le seul capable de propager des events a ses listeners)
		if (!addListeningSupport(object, provider)) {
			// FIXME un message ?
			return FALSE;
		}

		if (!isString(addListenerMethodName)) {
			addListenerMethodName = 'add' + eventType.capitalize() + 'Listener';
		}
		if (!isString(fireMethodName)) {
			fireMethodName = 'fire' + eventType.capitalize() + 'Event';
		}
		if (object[addListenerMethodName]) {
			errorMsg(CORE_PFX + '_addSimpleListeningSupport(): Cannot create method "#' + addListenerMethodName + '(). ' + ERR_ANOTHER_EXISTS);
			return FALSE;
		}
		if (object[fireMethodName]) {
			errorMsg(CORE_PFX + '_addSimpleListeningSupport(): Cannot create method "#' + fireMethodName + '(). ' + ERR_ANOTHER_EXISTS);
			return FALSE;
		}

		/**
		 * Cette méthode permet d'éxécuter une fonction seulement quand l'objet est prêt.
		 * Si l'objet est déjà prêt, la fonction sera exécutée "maintenant", de manière synchrone.
		 * Sinon, elle sera exécutée de manière asynchrone lorsque l'objet sera prêt.<br>
		 * <br>
		 * NOTA : il ne se passe rien si le paramètre donné est <code>null</code> ou n'est pas une
		 * fonction.
		 *
		 * @param {!Function} asyncFunction fonction à exécuter dés que l'objet est prêt.
		 */
		object[addListenerMethodName] = function(asyncFunction) {
			//On veut ici ne gérer que les événements de type choisi à l'avance.
			var wrapper = getWrapperFunction(object, asyncFunction);
			// On ne fait le traitement que si le wrapper a pu être créé
			// (la seule condition qui l'empêcherait est que le param donné soit invalide : nulle ou pas une fonction)
			if (wrapper) {
				// On ajoute la fonction à la liste des listeners de l'evenement demandé
				object[ADD_LISTENER_METHOD_NAME](eventType, wrapper);
			}
		};

		/**
		 * Fonction permettant de propager un event standard.<br>
		 * Il contiendra juste la donnée fournie en paramètre.
		 *
		 * @param {Object} data : donnée à propager comme contenu de l'event.
		 */
		object[fireMethodName] = function(data) {
			// On propage l'événement
			object.fireEvent(provider, eventType, data);
		};

		// tout s'est bien passé
		return TRUE;
	}

	// *************************************************
	// ***************** Ready Support *****************
	// *************************************************

	/**
	 * Permet d'ajouter à l'objet donné, une gestion particulière
	 * de son état d'utilisabilité.
	 *
	 * @param {!Object} object objet sur lequel il faut ajouter le Ready Support.
	 * @param {Object=} provider objet qui est seul à pouvoir envoyer des événements sur l'objet (facultatif ; par défaut c'est l'objet lui-même).
	 * @return {boolean} <code>true</code> si tout s'est bien passé, <code>false</code> sinon.
	 */
	function addReadySupport(object, provider) {
		// ajout du support d'événements simples de type 'ready' pour l'objet donné
		if (!addSimpleListeningSupport(object, 'ready', 'wait', 'setReady', provider)) {
			return FALSE;
		}

		function innerInit(data) {
			// data = 'already ready' si 'data' non défini
			if (data === UNDEFINED) {
				data = 'already ready';
			}

			// on accepte qu'un seul appel à 'setReady()', on considère l'objet "prêt" dés ce moment
			object.setReady = NULL;
			try { delete object.setReady; }catch (e) {/* rien a gérer ici, ça marche ou pas mais on s'en fout*/}

			/**
			 * On surcharge la méthode 'wait()' pour favoriser l'exécution directe de la
			 * fonction donnée.
			 *
			 * @param {!Function} asyncFunction fonction à exécuter.
			 */
			object.wait = function(asyncFunction) {
				// Même principe que la méthode d'avant...
				var wrapper = getWrapperFunction(object, asyncFunction);
				if (wrapper) {
					try {
						// ... sauf qu'on exécute directement la fonction "wrapper" en lui disant que data = 'already ready', sauf si on a choisi en paramètre
						wrapper.call(object, object, 'ready', data);
					}
					catch (exception) {
						errorMsg('core.readySupport[' + object + ']#wait(): \"direct\"-fire-exception = ' + exception);
					}
				}
			};

			// NOTA : à partir de là, toutes les fonctions donnée à 'wait()' ne seront plus mises
			// en liste d'attente mais exécutées directement
		}

		// On stocke la fonction 'setReady()' générée par le Simple Listening Support
		var innerSetReady = object.setReady;

		/**
		 * On surcharge la méthode 'setReady()', pour lui donner un comportement spécifique.<br>
		 * <br>
		 * Cette version de la méthode de propagation d'événement est prévue pour un appel unique.<br>
		 * Cela signifie qu'au premier appel à cette méthode, elle va disparaitre.<br>
		 * <br>
		 * Tant qu'elle n'a as été appelée, on peut accumuler des listeners dans une liste d'attente.
		 * (via la méthode 'wait()'). Lors de l'appel à la fonction 'setReady()', les listeners seront
		 * informés et la méthode 'wait()' sera modifiée, afin d'exécuter directement les fonctions
		 * reçues (au lieu de les mettre en file d'attente).
		 *
		 * @param {?Object=} data un objet qui peut contenir toutes sortes d'infos transmises par l'objet source.
		 */
		object.setReady = function(data) {
			// data = 'on ready' si 'data' non défini
			if (data === UNDEFINED) {
				data = 'on ready';
			}

			// si l'objet possède une méthode #init(), on l'exécute
			if (isFunction(object.init)) {
				object.init(data);
			}

			// On se met en attente pour faire le nettoyage ensuite
			object.wait(innerInit);

			// On appelle la méthode interne (privée), en lui donnant data
			innerSetReady.call(object, data);
		};

		// tout s'est bien passé, on retourne l'objet donné (ça a un côté pratique aussi ^^)
		return object;
	}


	// 'window.EV=' pour compatibilité ascendante (namespace EV) FIXME [ygally] à virer. Utiliser "window.ev"
	// on stocke les éléments d'easyvoyage dans la variable 'ev' (dans l'objet 'window')
	window.EV = (window.ev = {
		core: CORE,
		// Les configurations generales possibles
		conf: CONF,
		// Le gestionnaire de logs
		log: LOG,
		// La langue d'affichage de la page
		lang: LANG,
		// Les chemins généraux vers les contenus statiques
		path: PATH,
		// La fonction currentTimeMillis, donnant un timestamp en ms
		ctm: currentTimeMillis,
		// La fonction d'ajout de support d'événements
		addLS: addListeningSupport,
		// La fonction d'ajout de support simplifié d'événements
		addSLS: addSimpleListeningSupport,
		// La fonction d'ajout de support d'événements de type "ready" (basé sur le simplifié)
		addRS: addReadySupport
	});
	debugMsg(CORE_PFX + INIT_METHOD_NAME + ': OK');

	// raccourci vers le document principal de la fenetre
	var document = window.document,
			// raccourci vers le package principal d'easyvoyage (ev)
			ev = window.ev;

	/*###################################################################
	#####################################################################
	#####################################################################
	#####################################################################
	#####################################################################

	En dessous le Module ev.djs (Dynamic JavaScript) permettant de
	charger des scripts dynamiquement.

	#####################################################################
	#####################################################################
	#####################################################################
	#####################################################################
	###################################################################*/


	/**
	 * Module ev.djs : définition des outils de chargement dynamique
	 * de javascripts.
	 */
	(function() {
		// raccourci vers le premier HEAD trouvé dans le document
		var HEAD = document[ELTS_BY_TAGNAME]('head')[0],
				// constantes chaines utiles
				TAG_SCRIPT = 'script',
				// raccourcis vers le module courant et le module JS Directory (Bibliothèque de JS)
				DJS, JSD,
				// Liste de downloads
				downloadList = {},
				// Variable de travail contenant le tableau des modules à charger dés que djs est prêt
				wantedModules,
				// Types d'évènements
				EVENT_LOAD = 'load', EVENT_ERROR = 'error',
				// Nombre de secondes avant Timeout d'un load
				LOAD_TO = 10,
				// fonction qui ne fait rien
				EMPTY_FUNCTION = function() {},
				// expression régulière permettant de remplacer tous les points (ex: sur un namespace)
				RE_DOT = /\./g,
				// expression régulière permettant de repérer le scheme d'une URL
				RE_SCHEME = /^(https?:\/\/|@|\/)/,
				// expression régulière permettant de remplacer tous les caractères non alphanumériques
				RE_NON_ALHPA = /[^0-9A-Za-z]+/g,
				// expression régulière permettant de remplacer le jsessionid
				RE_JSESSIONID = /;jsessionid=[^?#]+([?#]|$)/;

		/**
		 * Fonction permettant de récupérer le fichier js
		 * dont le nom est donné.<br>
		 * Elle permet également de récupérer les modules à
		 * charger qui sont listés dans le paramètre 'load'
		 * (s'il existe) du fichier js donné.<br>
		 * Le fichier donné peut être n'importe où dans la
		 * page (à priori).
		 *
		 * @param {string} filename nom du fichier js.
		 */
		function getScriptLoadParam(filename) {
			// expression régulière de recherche du bon fichier
			//    var srcRegExp=new RegExp('^(.*)'+filename.replace(/\./g,'\\.')+'(\\?load=([^&]+))?(&|$)'),
			var srcRegExp = new RegExp('^(.*)' + filename.replace(/\./g, '\\.') + '(#load=([^&]+))?(&(.*))?$'),
					// get script nodes
					scriptElements = document[ELTS_BY_TAGNAME](TAG_SCRIPT),
					// variables de travail (longueur utilisée pour l'iteration, url du fichier, groupe de matching de l'expression régulière)
					i = scriptElements.length, url, j;
			while (i) {
				--i;
				url = scriptElements[i].src;
				if (url) {
					//debugMsg('djs#getScriptLoadParam(): looking for file '+filename+': '+url);
					//alert('djs#getScriptLoadParam(): looking for file '+filename+': '+url);
					//if (url && srcRegExp.test(url) && RegExp.$1 === PATH.js) {
					if (url && srcRegExp.test(url)) {
						var urlArr = (RegExp.$3 || '').split(/,/);
						//urlArr.params = (RegExp.$5 || '').split(/&/);
						var paramsArray = (RegExp.$5 || '').split(/&/);
						// ==> On dispose d'un tablzeau de string contenant "cle=valeure" dans paramsArray
						urlArr.params = {};
						if (paramsArray.length) {
							for (j = 0; j < paramsArray.length; ++j) {
								var cle = paramsArray[j].split('=')[0],
										value = paramsArray[j].split('=')[1];
								if (cle && value !== UNDEFINED) {// RegExp send undefined value.
									urlArr.params[cle] = value;
								}
							}
						}

						return urlArr;
					}
				}
			}
			return UNDEFINED;
		}

		/**
		 * Convertit un objet en chaine de caractères.
		 *
		 * @param {!(string|Object)} object objet à convertir en chaine.
		 * @param {*=} result inutile (sert de variable interne).
		 */
		function objectToString(object, result) {
			if (object.constructor === String) {
				return object;
			}
			result = '{const: ' + (object.constructor && object.constructor.name);
			var prop;
			for (prop in object) {
				if (object.hasOwnProperty(prop)) {
					if (isFunction(object[prop])) {
						result += ', ' + prop + '(){...}';
					}
					else {
						result += ', ' + prop + ': ' + object[prop];
					}
				}
			}
			return result + '}';
		}

		/**
		 * Fonction de création d'une gestion d'events pour
		 * tous les browsers pour un module donné en paramètre.
		 *
		 * @param {!string} modKey clé du module à charger.
		 * @return {!Function} la fonction callback générée.
		 */
		function createOnloadHandler(modKey) {
			var module = downloadList[modKey];
			// Si module introuvable, on renvoi rien
			// FIXME il faut afficher une erreur, car on ne devrait jamais passer là
			if (!module) {
				return EMPTY_FUNCTION;
			}

			// on planifie l'envoi de l'événement pour ce 1er demandeur du module
			module.wait(function() {
				debugMsg('djs#load(1st asker): module "' + objectToString(module) + '" is \'ready\'!');
				//        delay(function (){ module.fireEvent(DJS, EVENT_LOAD, {key: modKey, module: module.ask}); }, 500);
				module.fireEvent(DJS, EVENT_LOAD, {key: modKey, module: module.ask});
			});

			// on retourne la fonction qui sera appelée à chaque événement
			// navigateur pendant le chargement du module
			return function() {
				if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') {

					// on notifie l'objet qu'il peut s'initialiser et qu'il peut être considéré comme prêt
					// ceci enverra un event aux listeners éventuels
					module.setReady();
				}
				//      else{
				//debugMsg('djs#_createOnloadHandler(): readyState='+this.readyState+' ; modKey='+modKey);
				//      }
			};
		}

		/* On recherche des infos sur le script courant (cf. en dessous)
		 *
		 * Récupération du tableau contenant tous les modules souhaités
		 * (liste de noms de modules séparés par des virgules,
		 * donnée dans le paramètre 'load' du script courant
		 * et transformé en tableau de noms)
		 * FIXME les "getScriptLoadParam('/ev/tools.js')" et "getScriptLoadParam('/tools.js')" sont là pour compatibilité, il faut les virer quand on ne les utilise plus
		 */
		wantedModules = (getScriptLoadParam('/ev/boot.js') || getScriptLoadParam('/ev/boot0.js') || getScriptLoadParam('/ev/core.js') || getScriptLoadParam('/ev/tools.js') || getScriptLoadParam('/tools.js')) || [];
		//  wantedModules.push('csm');
		//debugMsg('djs#' + INIT_METHOD_NAME + ': path.js=' + PATH.js + ' ; modules=[' + wantedModules.toString() + ']');
		//function loadWantedModules(){
		//  var l=wantedModules.length, i, module;
		//  if ((JSD=ev.jsd) && isFunction(JSD.require)) {
		//    for(i=0; i<l; ++i){
		//      module=wantedModules[i];
		//      if(module){
		//debugMsg("djs#_loadWantedModules(): It's time to load module '"+module+"'");
		//        //delay(function (){ JSD.require(module, simpleOnAfter, simpleOnError); }, 10);
		//        JSD.require(module);
		//      }
		//    }
		//  }
		//  else{
		//errorMsg('djs#_loadWantedModules(): JS Directory loading failed! ['+PATH.js+'/ev/jsd.js]');
		//  }
		//}

		function defaultOnDone(source, eventType, data) {
			//debugMsg('djs#_defaultOnDone(): eventType:'+eventType+', key:' + (data&&data.key));
			var module = data && data.key && downloadList[data.key];
			if (!module) {
				errorMsg('djs#_defaultOnDone(): Module not found. [' + data.key + ']');
				return;
			}
			switch (eventType) {
				case EVENT_LOAD:
					//infoMsg('djs#_defaultOnDone(): Ok for '+data.key+' ('+(module&&module.u)+')');
					break;
				case EVENT_ERROR:
					errorMsg('djs#_defaultOnDone(): Error on ' + data.key + ' (' + (module && module.u) + ') [' + data.error + ']');
					break;
				default:
					warnMsg('djs#_defaultOnDone(): Unknown event type [src: ' + source + ', type: ' + eventType + ']');
					return;
			}
		}

		/**
		 * Expression régulière permettant de matcher les noms de groupes de modules
		 */
		var RE_GROUP = /^group\./;

		/**
		 * Permet de corriger le nom de groupe si les 3 conditions suivantes sont réunies :<br>
		 * - le nom donné est un nom de groupe de modules ;<br>
		 * - le mode optim est désactivé (via url ; optim:no) ;<br>
		 * - le niveau de verbosité est strictement supérieur à 2 (3=WARN minimum).
		 *
		 * @param {string} mod nom du module (ou groupe) en phase d'être téléchargé.
		 */
		function checkGroupOneByOneMode(mod) {
			if (!CONF.optim && CONF.verbose > 2 && RE_GROUP.test(mod)) {
				mod += '_mods';
			}
			return mod;
		}

		DJS = (ev.djs = {
			/**
				 * Charge dynamiquement un (ou plusieurs) fichiers js.
				 *
				 * @param {!(string|Object)} module an url, a namespace (ex: "ev.log") or an object with "ns", "url"
				 *   and optionally "async" properties
				 *   (ex: {ns: "ev.log", url: "http://code.jquery.com/jquery-1.4.4.js", async: true} ).
				 * @param {Function=} afterLoad (optionnel) after load callback (signature callback(source, eventType, data)...)
				 *   'data' contains an object like {module: value of given "module" param...}.
				 * @param {Function=} afterError (optionnel) after error callback (signature callback(source, eventType, data)...)
				 *   'data' contains an object like {module: value of given "module" param..., error: the error that occured...}.
				 */
			load: function(module, afterLoad, afterError) {
				var modKey, modUrl, script;
				if (isString(module)) {
					//fatalMsg('LOAD######## String '+module);
					if (RE_SCHEME.test(module)) {
						//fatalMsg('LOAD######## Scheme '+module);
						module = module.replace(/^@/, '');
						modKey = module.replace(RE_JSESSIONID, '$1').replace(RE_NON_ALHPA, '_');
						modUrl = module;
					}
					else {//FIXME voir si on le permet c'est peut-être pas indispensable
						//fatalMsg('LOAD######## NOT Scheme '+module);
						modKey = module.replace(RE_DOT, '_');
						modUrl = PATH.js + '/' + checkGroupOneByOneMode(module).replace(RE_DOT, '/') + '.js';
					}
				}
				else if (module && module.ns) {
					//fatalMsg('LOAD######## NOT String '+module.ns+', '+module.url+', '+module.async);
					modKey = module.ns.replace(RE_DOT, '_');
					if (!module.url) {
						module.url = PATH.js + '/' + checkGroupOneByOneMode(module.ns).replace(RE_DOT, '/') + '.js';
					}
					modUrl = module.url;
				}
				else {
					errorMsg('djs#load(): "module" param should be : an url, a namespace or an object with "ns", "url" and optionally "async" properties [' + objectToString(module) + ']');
					return;
				}

				afterLoad = afterLoad || defaultOnDone;
				afterError = afterError || defaultOnDone;
				if (downloadList[modKey]) {
					// S'il est déjà prévu de télécharger ce module (ou s'il est déjà téléchargé), on sort
					// FIXME accepter de nouveaux listeners onload/onerror si définis plutôt que de laisser tomber complètement
					infoMsg('djs#load(): module "' + objectToString(module) + '" already asked. Waiting for \'ready\' event...');
					downloadList[modKey].wait(function() {
						infoMsg('djs#load(): module "' + objectToString(module) + '" is \'ready\'!');
						afterLoad(DJS, EVENT_LOAD, {key: modKey, module: module.ask, url: modUrl});
					});
					return;
				}
				//        fatalMsg('djs#load(): looooooaaaading module "' + modUrl + '"...');
				module = (downloadList[modKey] = {
					ask: module,
					//        k: modKey,
					s: document[CREATE_ELEMENT_NS] ? document[CREATE_ELEMENT_NS](W3_XHTML, TAG_SCRIPT) : document[CREATE_ELEMENT](TAG_SCRIPT),
					u: modUrl,
					a: !!module.async
					//        ,
					//        a: afterLoad,
					//        e: afterError
				});
				//fatalMsg('LOAD######## Module = {ask:'+module.ask+',key:'+modKey+',url:'+module.u+',async:'+module.a+'}');

				// on ajoute le support d'évènements sur le module
				addListeningSupport(module, DJS);
				// on ajoute le support "ready" sur le module (une extension du listening support)
				addReadySupport(module, DJS);

				/**
					 * Méthode d'initialisation du module à la fin du téléchargement.
					 * Elle sera exécutée par le "ready support" à l'appel de "setReady()".
					 */
				module.init = function() {
					debugMsg('djs#load(): module "' + objectToString(module) + '" initializing...');
					// on signale le module comme 'ok'
					module.ok = TRUE;
					// on supprime le script de son conteneur pour éviter les "memory leak" (notamment sur IE)
					module.s.onload = module.s.onreadystatechange = NULL;
					if (module.s.parentNode) {
						module.s.parentNode.removeChild(module.s);
					}
				};

				if (isFunction(afterLoad)) {
					// ajout d'un listener pour l'evenement LOAD
					module[ADD_LISTENER_METHOD_NAME](EVENT_LOAD, afterLoad);
				}
				if (isFunction(afterError)) {
					module[ADD_LISTENER_METHOD_NAME](EVENT_ERROR, afterError);
				}

				//debugMsg('djs#load(): "module" param : '+objectToString(module));
				script = module.s;
				//alert('script='+script);
				// Cela n'est pas obligatoire (c'est le type par défaut)
				//script.type='text/javascript';
				script.charset = 'iso-8859-1';
				script.src = modUrl;
				script.defer = script.async = module.a;
				//      script.defer=script.async=FALSE;
				// ajout d'events de chargement pour tous browsers
				script.onload = script.onreadystatechange = createOnloadHandler(modKey);
				HEAD.appendChild(script);
				delay(function() { if (!module.ok) { module.fireEvent(DJS, EVENT_ERROR, {key: modKey, module: module.ask, error: 'djs#load()> Time Out ' + LOAD_TO + 's'}); } }, LOAD_TO * 1000);
			}
			//  ,

			//    /**
			//     * Méthode exécutée après le chargement du fichier demandé.
			//     * (utilisation de l'adaptateur de CallBack callbackAdaptor())
			//     */
			//    done: function (){ /*callbackAdaptor(calleeCallerName(arguments), 'a');*/ },
			//    error: function (){ callbackAdaptor(calleeCallerName(arguments), 'e'); }
			//    done: function (){
			//      var n=calleeCallerName(arguments), module=downloadList[n];
			//      delay( function (){ if(module&&module.a&&isFunction(module.a)){ module.a(n, module.u); } }, 0);
			//    },
			//    error: function (){
			//      var n=calleeCallerName(arguments), module=downloadList[n];
			//      delay(function (){ if(module&&module.e&&isFunction(module.e)){ module.e(n, module.u); } }, 0);
			//    }
		});

		debugMsg('djs#' + INIT_METHOD_NAME + ': OK');




		/*###################################################################
		#####################################################################
		#####################################################################
		#####################################################################

		Ci-dessous le module JSD (ev.jsd : JS Directory) représentant la
		bibliothèque JS.

		#####################################################################
		###################################################################*/

		(function() {
			var RE_DOT = /\./g,
					RE_KEY = RE_DOT,// /[.\-]/g,
					RE_READY_SUPPORTED = /^(group\..+|ev\.esv\.main|ev\.esv\.initforms)$/,
					readySupported = {},
					readyError = 'Name must be non-null and non-empty string.';

			function addNoneOptimPath(file) {
				return PATH.js.replace(RE_JS_OPTIMIZED, '$1') + file;
			}

			function getReadySupportFor(name) {
				// La vérification d'existance permet de ne pas écraser
				if (!readySupported[name]) {
					// si l'objet de référence existe déjà on ne fait rien
					readySupported[name] = addReadySupport({name: name});
				}
				return readySupported[name];
			}

			function setReadyOn(module, data) {
				if (!module || !isString(module)) { throw 'jsd#_setReadyOn(): ' + readyError; }
				//debugMsg('jsd#_setReady(' + module + '): setting ready state... [' + getReadySupportFor(module.replace(RE_KEY, '_')).name + ']');
				getReadySupportFor(module.replace(RE_KEY, '_')).setReady(data);
				infoMsg('jsd#_setReady(' + module + '): ready set! [' + getReadySupportFor(module.replace(RE_KEY, '_')).name + ']');
			}

			function waitFor(module, func) {
				if (!module || !isString(module)) { throw 'jsd#_waitFor(): ' + readyError; }
				getReadySupportFor(module.replace(RE_KEY, '_')).wait(func);
				infoMsg('jsd#_waitFor(' + module + '): wait started! [' + getReadySupportFor(module.replace(RE_KEY, '_')).name + ']');
			}

			/**
			 * Raccourci vers le constructeur de module.
			 *
			 * @param {!(Object.<string, string>|string)} module nom du package (namespace) du module
			 *   (ou objet le représentant, contenant le namespace dans une propriété 'ns').
			 * @param {number=} build numéro (build number) du module [optionnel seulement si le module donné est un objet].
			 * @param {string=} optimizedPath [optionnel] permet de préciser le type
			 *   d'optimisation choisie (répertoire relatif correspondant
			 *   à l'optimisation choisie ; ex: "optim/gg_cc") pour le
			 *   module. [Par défaut : aucune optimisation ; c-a-d. le
			 *   même mode que celui de la page (cf. variable ev.path)].
			 */
			function addModule(module, build, optimizedPath) {
				//debugMsg('jsd#_addModule(): defining "'+module+'"... 0');
				if (isString(module)) {
					if (!isNumber(build)) { throw 'jsd#_addModule(): le numéro de "build" doit être un nombre. [' + build + ']'; }
					// construction du module à partir du nom de namespace
					module = {
						ns: module, // nom du package
						bld: '?b' + build,
						async: TRUE // par défaut, chargement asynchrone
					};
				}
				if (!module) {
					throw 'jsd#_addModule(): l\'object module n\'est pas correct [' + module + ']';
				}
				if (!isString(module.ns)) {
					throw 'jsd#_addModule(): la propriété "ns" du module doit être définie. [' + module.ns + ']';
				}
				//debugMsg('jsd#_addModule(): defining "'+module.ns+'"... 1');
				// si la propriété 'bld' (suffixe de build) n'est pas défini, on le fixe à ''
				if (!module.bld) {
					module.bld = '';
				}
				//debugMsg('jsd#_addModule(): defining "' + module.ns + '"... 2 [bld: ' + module.bld + ']');
				// si aucune url définie, définition de l'URL complète
				if (!module.url) {
					// chemin relatif (initial) du module
					var path = module.ns.replace(RE_DOT, '/') + '.js';
					//debugMsg('jsd#_addModule(): defining "' + module.ns + '"... 2.1 [path: ' + path + ']');
					// potentielle correction du chemin en fonction du paramètre d'optimisation
					// (si et seulement si le chemin n'est pas déjà un chemin d'optimisation)
					if (optimizedPath) {
						path = addNoneOptimPath('/' + optimizedPath + '/' + path);
					}
					else {
						path = PATH.js + '/' + path;
					}
					//debugMsg('jsd#_addModule(): defining "' + module.ns + '"... 2.2 [path: ' + path + ']');
					module.url = path + module.bld;
				}
				infoMsg('jsd#_addModule(): pre-defining "' + module.ns + '"... 3 [url: ' + module.url + ' ; ok: ' + module.ok + ']');
				// stockage du module dans la bibliothèque
				JSD[module.ns.replace(RE_KEY, '_')] = module;
				//debugMsg('jsd#_addModule(): ' + module.ns.replace(RE_KEY, '_') + ' ==> ' + (JSD[module.ns.replace(RE_KEY, '_')] && objectToString(JSD[module.ns.replace(RE_KEY, '_')])));
			}

			/**
			 * Méthode permettant de déclencher le chargement d'un
			 * module s'il n'est pas déjà dans la page.
			 * @param {!string} module nom de package (namespace) du module à charger.
			 * @param {Function=} onLoad fonction callback (optionnel) à appeler après le chargement.
			 * @param {Function=} onError fonction callback (optionnel) à appeler en cas d'erreur.
			 * @return {boolean} false si quelque chose s'est mal passé, sinon true.
			 */
			function requireOne(module, onLoad, onError) {
				// clé du module permettant de lister ceux qui sont déjà chargés
				var modKey = isString(module) && module.replace(RE_KEY, '_'),
						// variable référençant le module
						mod;
				if (!modKey) {
					errorMsg('jsd#_requireOne(): the given module key ' + ERR_IS_NOT_VALID + '. [' + module + ']');
					return FALSE;
				}
				// récupération du module (s'il est défini dans la bibliothèque de modules)
				mod = JSD[modKey];
				//debugMsg('jsd#_requireOne(): ' + modKey + ' ==> ' + (mod && objectToString(mod)));
				/**
				 * Permet d'encapsuler le callback et d'effectuer une
				 * tâche avant de l'appeler : informer la bibliothèque
				 * que le module est chargé.
				 */
				function onLoadWrapper() {
					mod.ok = TRUE;
					//debugMsg('jsd#_requireOne_onLoadWrapper(): module is now OK [' + objectToString(module) + ']');
					if (isFunction(onLoad)) {
						onLoad.apply(window, arguments);
					}
				}
				if (!mod) {
					mod = (JSD[modKey] = {ns: module, async: TRUE});
					infoMsg('jsd#_requireOne(): The wanted module is not in JS Directory. Nevermind! Trying to load... ["' + module + '"]');
					DJS.load(mod, onLoadWrapper, onError);
				}
				else {
					if (mod.ok) {
						//debugMsg('jsd#_requireOne(): the wanted module ("' + module + '") is already loaded.');
						if (isFunction(onLoad)) {
							onLoad(DJS, EVENT_LOAD, {key: modKey, module: module, url: mod.url || '#pre-loaded#'});
						}
					}
					else {
						//debugMsg('jsd#_requireOne(): loading module with DJS... [' + objectToString(module) + ']');
						DJS.load(mod, onLoadWrapper, onError);
					}
				}
				return TRUE;
			}

			/**
			 * Méthode permettant de déclencher le chargement d'un
			 * module s'il n'est pas déjà dans la page et d'attendre
			 * qu'il soit vraiment prêt.
			 * @param {!string} module nom de package (namespace) du module à charger.
			 * @param {Function=} onLoad fonction callback (optionnel) à appeler après le chargement.
			 * @param {Function=} onError fonction callback (optionnel) à appeler en cas d'erreur.
			 * @return {boolean} false si quelque chose s'est mal passé, sinon true.
			 */
			function requireAndWait(module, onLoad, onError) {
				if (!module) { throw 'jsd#requireAndWait(): module is not defined.'; }
				// on déclenche le require standard (pour un seul module)
				return requireOne(module, function() {
					// on exécute le onLoad seulement quand le module est prêt
					// [il faudra que celui soit défini comme prêt (avec la méthode ev.jsd.setReady()) dans le fichier correspondant téléchargé]
					infoMsg('jsd#_requireAndWait(): "' + module + '" module loaded, waiting for ready state ok...');
					waitFor(module, onLoad);
				}, onError);
			}

			/**
			 * Méthode permettant de déclencher le chargement de
			 * module s'ils ne sont pas déjà dans la page.
			 *
			 * @param {!Array.<string>} modules tableau des noms de packages (namespaces) des modules à charger.
			 * @param {Function=} onLoad fonction callback (optionnel) à appeler après le chargement.
			 * @param {Function=} onError fonction callback (optionnel) à appeler en cas d'erreur.
			 * @return {boolean} false si quelque chose s'est mal passé, sinon true.
			 */
			function require(modules, onLoad, onError) {
				var requireMethod = requireOne;

				if (isString(modules[0]) && RE_READY_SUPPORTED.test(modules[0])) {
					infoMsg('jsd#_require(): module recognized : ' + modules[0] + ' => using ready support');
					requireMethod = requireAndWait;
				}

				//debugMsg('jsd#_require(): loading "' + modules.join(', ') + '" with callbacks :\nload="' + onLoad + '"\nand error="' + onError + '"');
				if (modules.length > 1) {
					// S'il y a encore des choses à charger après, on crée un callback qui chargera les suivants en cascade
					return requireMethod(modules[0], function(a, b, data) {
						debugMsg('jsd#_require(): on load (' + a + ', ' + b + ', ' + (data && data.key) + '). Processing next...');
						modules.shift();
						return require(modules, onLoad, onError);
					}, onError);
				}
				// sinon (c'est la condition de sortie), on utilise les callbacks fournis
				debugMsg('jsd#_require(): loading last module "' + modules[0] + '" with callbacks :\nload="' + onLoad + '"\nand error="' + onError + '"');
				return requireMethod(modules[0], onLoad, onError);
			}

			function createOnLoadWrapper(modules, modCheckerArray, indice, okCallback) {
				/**
				 * Permet d'encapsuler le callback et d'effectuer une
				 * tâche avant de l'appeler (ou pas) : vérifier que les
				 * autres modules ont été chargés entre temps. Si ce
				 * n'est pas le cas on ne fait rien, le callback sera
				 * exécuté par un autre wrapper (sur un des autres mod).
				 */
				return function(a, b, data) {
					// on met une chaine vide dans la case correspondant au module à l'intérieur du tableau
					modCheckerArray[indice] = '';
					// si la chaine représentant le tableau est vide c'est que tous les modules sont arrivés
					if (!modCheckerArray.join('')) {
						debugMsg('jsd#_simRequire_onLoadWrapper(): sim. mod #' + indice + '. (' + b + ', ' + (data && data.key) + '). Done! => calling callback? ' + isFunction(okCallback));
						if (isFunction(okCallback)) {
							okCallback.apply(window, arguments);
						}
					}
					else {
						debugMsg('jsd#_simRequire_onLoadWrapper(): sim. mod #' + indice + '. (' + b + ', ' + (data && data.key) + '). => waiting for "' + modCheckerArray.join('') + '"...');
					}
				};
			}

			/**
			 * Méthode permettant de déclencher le chargement de
			 * module s'ils ne sont pas déjà dans la page.<br>
			 * <b>Nota :</b> cette version fait un chargement
			 * simultané de tous les modules demandés.
			 *
			 * @param {!Array.<string>} modules tableau des noms de packages (namespaces) des modules à charger.
			 * @param {Function=} onLoad fonction callback (optionnel) à appeler après le chargement.
			 * @param {Function=} onError fonction callback (optionnel) à appeler en cas d'erreur.
			 * @return {boolean} false si quelque chose s'est mal passé, sinon true.
			 */
			function simRequire(modules, onLoad, onError) {
				var modCnt = modules.length, i, modChecker, requireMethod, ok = true;
				modChecker = [];
				for (i = 0; i < modCnt; ++i) {
					modChecker[i] = i;
					requireMethod = requireOne;
					// choix de la méthode de chargement
					if (isString(modules[i]) && RE_READY_SUPPORTED.test(modules[i])) {
						infoMsg('jsd#_simRequire(): module recognized : ' + modules[i] + ' => using ready support');
						requireMethod = requireAndWait;
					}
					ok = ok && requireMethod(modules[i], createOnLoadWrapper(modules, modChecker, i, onLoad), onError);
				}
				return ok;
			}

			function checkRequireParams(args) {
				var mods = [], m = 0, len = args.length, ldCallback, errCallback;
				if (len > 1) {
					--len;
					errCallback = args[len];
					if (isFunction(errCallback)) {
						if (len > 1) {
							--len;
							ldCallback = args[len];
							if (isNotFunction(ldCallback)) {
								++len;
								ldCallback = errCallback;
								errCallback = UNDEFINED;
							}
						}
						else {
							ldCallback = errCallback;
							errCallback = UNDEFINED;
						}
					}
					else {
						++len;
						errCallback = UNDEFINED;
					}
				}
				while (m < len) {
					mods.push(args[m]);
					++m;
				}
				return {mods: mods, ldCB: ldCallback, errCB: errCallback};
			}

			// Construction de l'annuaire
			JSD = (ev.jsd = {
				/**
					 * Accesseur vers l'outil de chargement de modules.
					 *
					 * params : tous les (noms de packages / namespaces) des
					 * modules à charger, suivi optionnellement par un callback
					 * pour l'après chargement, et éventuellement un callback
					 * en cas d'erreur.
					 */
				require: function() {
					var modObject = checkRequireParams(arguments);
					//debugMsg('jsd#require(): invoking require() with modules=' + modObject.mods.length + ' load=' + modObject.ldCB + ' err=' + modObject.errCB);
					return require.call(window, modObject.mods, modObject.ldCB, modObject.errCB);
				},

				/**
					 * Accesseur vers l'outil de chargement simultané de modules.
					 *
					 * params : tous les (noms de packages / namespaces) des
					 * modules à charger, suivi optionnellement par un callback
					 * pour l'après chargement, et éventuellement un callback
					 * en cas d'erreur.
					 */
				simRequire: function() {
					var modObject = checkRequireParams(arguments);
					//debugMsg('jsd#simRequire(): invoking simRequire() with modules=' + modObject.mods.length + ' load=' + modObject.ldCB + ' err=' + modObject.errCB);
					return simRequire.call(window, modObject.mods, modObject.ldCB, modObject.errCB);
				},

				///**
				// * Accesseur vers la méthode permettant de déclencher le chargement
				// * d'un module s'il n'est pas déjà dans la page et d'attendre
				// * qu'il soit vraiment prêt.
				// * @param {!string} module nom de package (namespace) du module à charger.
				// * @param {!string} readyRef nom de l'objet qui servira de repère pour savoir si le module est prêt.
				// * @param {Function=} onLoad fonction callback (optionnel) à appeler après le chargement.
				// * @param {Function=} onError fonction callback (optionnel) à appeler en cas d'erreur.
				// * @return {boolean} false si quelque chose s'est mal passé, sinon true.
				// */
				//requireAndWait: function (module, onLoad, onError) {
				//  requireAndWait.call(window, module, onLoad, onError);
				//},

				/**
					 * Accesseur vers l'outil d'ajout de modules.
					 *
					 * @param {!(Object|string)} module nom du package (namespace) du module (ou objet le représentant).
					 * @param {!number} build numéro (build number) du module.
					 * @param {string=} optimizedPath [optionnel] permet de préciser le type
					 *   d'optimisation choisie (répertoire relatif correspondant
					 *   à l'optimisation choisie ; ex: "optim/gg_cc") pour le
					 *   module. [Par défaut : aucune optimisation ; c-a-d. le
					 *   même mode que celui de la page (cf. variable ev.path)].
					 */
				addModule: function(module, build, optimizedPath) {
					addModule.call(window, module, build, optimizedPath);
				},

				setReady: function(module, data) {
					setReadyOn.call(window, module, data);
				},

				wait: function(module, func) {
					waitFor.call(window, module, func);
				}
			});

			// Modules considérés comme déjà existants
			addModule({ns: 'ev.core', ok: 1});
			addModule({ns: 'ev.tools', ok: 1});
			addModule({ns: 'tools', ok: 1});
			// Modules prédéfinis dans la bibliothèque standard (ils vont être chargés automatiquement ; cf. plus bas)
			addModule('ev.20100903', 201110051720, 'optim/gg_cc');
			addModule('ev.lang.mapper', 201105261557, 'optim/gg_cc');
			addModule('ev.modules', ts_start, 'optim/gg_cc');

			// LES MODULES EXTERNES DOIVENT ETRE EN URL ABSOLUE (avec http://)
			// OU EN VERSION NON COMPRESSEE (même en cas d'optim des pages)

			// Modules externes compressés
			//addModule({ns: 'jquery', url: '//code.jquery.com/jquery-1.7.1.min.js', async: TRUE});
			addModule({ns: 'jquery', url: '//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js', async: TRUE});
			addModule({ns: 'jquery.ui', url: '//ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js', async: TRUE});
			addModule({ns: 'jquery.ui.stars', url: addNoneOptimPath('/jquery/jquery.ui.stars.min.js'), async: TRUE});
			addModule({ns: 'jquery.jcarousel', url: addNoneOptimPath('/jquery/jquery.jcarousel.min.js'), async: TRUE});
			addModule({ns: 'jquery.colorbox', url: addNoneOptimPath('/jquery/jquery.colorbox-min.js'), async: TRUE});
			//addModule({ns: 'ext.swfobject', url: HTTP_SCHEME + 'ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js', async: TRUE});
			addModule({ns: 'ext.swfobject', url: HTTP_SCHEME + 'panorama.easyvoyage.com/js/swfobject.js', async: TRUE});
			addModule({ns: 'ext.swfmacmousewheel', url: HTTP_SCHEME + 'panorama.easyvoyage.com/js/swfmacmousewheel.js', async: TRUE});
			addModule({ns: 'ext.smartAdServer', url: HTTP_SCHEME + 'cdn1.smartadserver.com/diff/js/smartadserver.js', async: TRUE});
			addModule({ns: 'ext.ea', url: '//ea.easyvoyage.com/ea.js', async: TRUE});

			// Modules externes non-compressés
			addModule({ns: 'src.jquery', url: '//ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.js', async: TRUE});
			addModule({ns: 'src.jquery.ui', url: '//ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.js', async: TRUE});
			addModule({ns: 'src.jquery.jcarousel', url: addNoneOptimPath('/jquery/jquery.jcarousel.js'), async: TRUE});
			addModule({ns: 'jquery.nivo.slider.pack', url: addNoneOptimPath('/jquery/jquery.nivo.slider.pack.js'), async: TRUE});
			addModule({ns: 'jquery.datePicker', url: addNoneOptimPath('/jquery/jquery.datePicker.js'), async: TRUE});
			addModule({ns: 'jquery.datePickerMultiMonth', url: addNoneOptimPath('/jquery/jquery.datePickerMultiMonth.js'), async: TRUE});
			addModule({ns: 'jquery.easing', url: addNoneOptimPath('/jquery/jquery.easing.js'), async: TRUE});
			//addModule({ns: 'src.ext.swfobject', url: HTTP_SCHEME + 'ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject_src.js', async: TRUE});
			addModule({ns: 'src.ext.swfobject', url: addNoneOptimPath('/ext/swfobject_src.js'), async: TRUE});

			// Modules externes exceptionnels (compressibles car valides et stockés chez Easy)
			addModule('ext.date', 201104191430);

			debugMsg('jsd#' + INIT_METHOD_NAME + ': OK');
			//  }
		}()); // Fin de ev.djs - Exécution

		/*###################################################################
		###################################################################*/

		// Ajout du Ready Support sur notre principal package 'ev'
		// Celui-ci sera considéré comme prêt lorsque le module de sec mngr est prêt
		addReadySupport(ev);

		/**
		 * Cette fonction est un callback qui va se lancer
		 * via le se-cu-rity Mn-gr.
		 *
		 * @param {?*=} source objet source de l'appel (c'est notre fameux ... ^^).
		 */
		function realPostLoadStart(source) {
			// On s'occupe des choses importantes (se-cu - ri-ty machin)
			if (source && source.src && ev[source.src]) {
				// mapping de la fonction
				if (addDelegate(ev[source.src], source.nom, source.job)) {
					// appel de la fonction setReady sur l'objet si elle existe
					if (isFunction(ev[source.src].setReady)) {
						debugMsg('0903#' + INIT_METHOD_NAME + ': sec mngr ready');
						// choix des bonnes cl-és (pour Easy et/ou par défaut) passées en paramètre des listeners d'event "onready"
						ev[source.src].setReady({
							dka: '78abT1p2B_CXoSmEqkl-rOefLM4Ru9HI5QNx6vwDhVWyJKsUtYZ0gF3zAnPcdGij',
							dkpv: DN.replace(/[.]/g, '_') || '3wIFzGG2E7mQExVHt77S2V8w'
						});
					}
					debugMsg('0903#' + INIT_METHOD_NAME + ': sec mngr initialized!');
				}
			}

			// On signale aux listeners du package 'ev' que c'est prêt (ev prêt quand le sec mngr est initialisé)
			ev.setReady();
			//LOG.level = 5;
			var l = wantedModules.length, i, module;
			if (!window.evData) {
				window.evData = {};
			}
			for (var cle in wantedModules.params) {
				if (wantedModules.params.hasOwnProperty(cle)) {
					debugMsg(cle + '=====' + wantedModules.params[cle]);
					//if(!window.evData[cle]) { // à priori on surcharge si précisé dans l'ancre // FIXME test à virer
					window.evData[cle] = wantedModules.params[cle];
					debugMsg('window.evData[cle]=' + window.evData[cle]);
					//}
					// SI variabel de LANG on doit mettre à jour LANG avec la nouvelle valeur.
					if (cle === 'lang') {
						LANG = ev.lang;
						LANG.current = ev.lang.current = wantedModules.params[cle];
						LANG.language = ev.lang.language = wantedModules.params[cle].replace(/_.*$/, '');
						LANG.country = ev.lang.country = wantedModules.params[cle].replace(/^.*_/, '');
					}
				}
			}


			//      fatalMsg('boot-mods#' + INIT_METHOD_NAME + ': '+l+' module(s) to post load.');
			// Ensuite, on charge les modules qui ont été demandés
			for (i = 0; i < l; ++i) {
				module = wantedModules[i];
				if (module) {
					//          fatalMsg('boot-mods#' + INIT_METHOD_NAME + ': module '+module+' to post load.');
					//debugMsg("djs#realPostLoadStart(): it's time to load module '"+module+"'");
					if (RE_SCHEME.test(module)) {
						//debugMsg("djs#realPostLoadStart(): going on with djs.load() [mod "+module+"]");
						DJS.load(module);
					}
					//        else if(module.charAt(0)=='@'){
					//          DJS.load({ns: module, url: module.substring(1), async: TRUE});
					//        }
					else {
						//debugMsg("djs#realPostLoadStart(): going on with jsd.require() [mod "+module+"]");
						//          DJS.load({ns: module, async: TRUE});
						JSD.require(module);
					}
				}
			}
		}

		// Ajout du Ready Support sur notre principal module (comme s'il en avait besoin ; cf. mod 0903)
		addReadySupport(CORE);

		/**
		 * Fonction à exécuter à l'arrivée du module du 20100903.
		 */
		function onSecReady() {
			//FIXME remettre les logs (à WARN) à l'arrivée du module 20100903
			// On attends "soit disant" le "core object" , mais il est déjà là
			// (en fait c'est se-cu-rity machin qui gère le ready support du "core object")
			// et on execute la fonction qui va tout régler ^^ !
			CORE.wait(realPostLoadStart);
		}

		// On utilise une première fois le module pour charger la bibliothèque de modules JS (JS Directory)
		//  DJS.load(HTTP_SCHEME + 'code.jquery.com/jquery-1.4.4.js', function (){ debugMsg('djs#afterLoad(): done jquery!'); });
		//  DJS.load(HTTP_SCHEME + 'code.jquery.com/jquery-1.4.4.min.js', function (){ debugMsg('djs#afterLoad(): done jquery!'); });
		//  DJS.load(HTTP_SCHEME + 'code.jquery.com/jquery-1.4.4.min.js');
		//FIXME supprimer les logs (<WARN) jusqu'à l'arrivée du module 20100903
		//  DJS.load('ev.20100903', onSecReady);
		JSD.simRequire('ev.20100903', 'ev.modules', 'ev.lang.mapper', onSecReady);
	}()); // Fin de ev.djs - Exécution


	/*###################################################################
	#####################################################################
	#####################################################################

	En dessous l'ancien fichier base/js/tools.js utilisé partout.
	Ceci, en attendant de migrer vers l'utilisation unique de ev/core.js

	Pour le moment, les 3 JS suivants sont identiques :
	- js/ev/core.js
	- js/ev/tools.js
	- js/tools.js

	#####################################################################
	###################################################################*/


	/**
	 * Module ev.tools : définition des outils généraux.
	 */
	(function() {
		// raccourcis vers la méthode interdite 'eval()'
		var EVAL = eval,
				// Expression régulière permettant de chercher une chaine de plusieurs espaces (2 ou +)
				REGEXP_SPACE = / {2,}/g;

		// FIXME [ygally] A mettre dans un fichier à part (tout ce qui concerne la langue en js : /base/js/ev/lang.js)
		// 4 variables internes : accessibles seulement dans cette
		// fonction anonyme et dans les fonctions créées plus bas.
		// Ceci évite de créer trop de variables globales (seules
		// les 4 fonctions, plus bas, seront accessibles)
		var tabJoursTexteCourt, // jours courts
				tabJoursTexteLong, // jours entiers
				tabMoisTexteCourt, // mois courts
				tabMoisTexteLong, // mois entiers
				submitValueSubmitME,
				waitValueSubmitME;
		// définition du vocabulaire selon la langue
		// par défaut en français.
		switch (LANG.current) {
			case 'es_ES':
				tabJoursTexteCourt = ['dom.', 'lun.', 'mar.', 'mié.', 'jue.', 'vie.', 'sáb.'];
				tabJoursTexteLong = ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'];
				tabMoisTexteCourt = ['', 'ene.', 'feb.', 'mar.', 'abr.', 'may.', 'jun.', 'jul.', 'ago.', 'sep.', 'oct.', 'nov.', 'dic.'];
				tabMoisTexteLong = ['', 'enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'];
				submitValueSubmitME = 'Buscar';
				waitValueSubmitME = 'Espera';
				break;
			case 'it_IT':
				tabJoursTexteCourt = ['dom.', 'lun.', 'mar.', 'mer.', 'gio.', 'ven.', 'sab.'];
				tabJoursTexteLong = ['domenica', 'lunedi', 'martedi', 'mercoledi', 'giovedi', 'venerdi', 'sabato'];
				tabMoisTexteCourt = ['', 'gen.', 'feb.', 'mar.', 'apr.', 'mag.', 'giu.', 'lug.', 'ago.', 'set.', 'ott.', 'nov.', 'dic.'];
				tabMoisTexteLong = ['', 'gennaio', 'febbraio', 'marzo', 'aprile', 'maggio', 'giugno', 'luglio', 'agosto', 'settembre', 'ottobre', 'novembre', 'dicembre'];
				submitValueSubmitME = 'Ricercare';
				waitValueSubmitME = 'In Corso';
				break;
			case 'en_US': // ici en_US = en_GB, mais il peut y avoir des différences pour certains textes
			case 'en_GB':
				tabJoursTexteCourt = ['sun.', 'mon.', 'tue.', 'wed.', 'thu.', 'fri.', 'sat.'];
				tabJoursTexteLong = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
				tabMoisTexteCourt = ['', 'jan.', 'feb.', 'mar.', 'apr.', 'may', 'jun.', 'jul.', 'aug.', 'sep.', 'oct.', 'nov.', 'dec.'];
				tabMoisTexteLong = ['', 'january', 'febrary', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december'];
				submitValueSubmitME = 'Search';
				waitValueSubmitME = 'Loading';
				break;
			case 'de_DE':
				tabJoursTexteCourt = ['So.', 'Mo.', 'Di.', 'Mi.', 'Do.', 'Fr.', 'Sa.'];
				tabJoursTexteLong = ['Sonntag', 'Montag', 'Dienstag', 'Mercredi', 'Donnerstag', 'Freitag', 'Samstag'];
				tabMoisTexteCourt = ['', 'Jan.', 'Feb.', 'M\u00E4rz.', 'Apr.', 'Mai.', 'Juni.', 'Juli.', 'Aug.', 'Sep.', 'Okt.', 'Nov.', 'Dez.'];
				tabMoisTexteLong = ['', 'Januar', 'Februar', 'M\u00E4rz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'];
				submitValueSubmitME = 'Suche';
				waitValueSubmitME = 'Suche';
				break;
				//case "fr_FR": par défaut fr_FR (français)
			default:
				tabJoursTexteCourt = ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'];
				tabJoursTexteLong = ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'];
				tabMoisTexteCourt = ['', 'jan.', 'fév.', 'mar.', 'avr.', 'mai', 'jun.', 'jul.', 'aoû.', 'sep.', 'oct.', 'nov.', 'déc.'];
				tabMoisTexteLong = ['', 'janvier', 'février', 'mars', 'avril', 'mai', 'juin', 'juillet', 'août', 'septembre', 'octobre', 'novembre', 'décembre'];
				submitValueSubmitME = 'Rechercher';
				waitValueSubmitME = 'En cours';
				break;
		}

		/**
		 * Fonction permettant, si besoin, de définir une fonction
		 * à exécuter après le chargement du fichier donné.
		 *
		 * A priori cette fonction doit être utilisée en fin de fichier
		 * et prendre en paramètre le nom du fichier en question.
		 *
		 * @param {string} jsFile : nom du fichier js.
		 * @deprecated ne doit plus être utilisée, puis à terme, doit être supprimée.
		 */
		function onFileLoad(jsFile) {
			// expression reguliere permettant de retrouver le ficher demandé
			var PAT_JS_ONLOAD = new RegExp('^.*\\/' + jsFile.replace(/\./g, '\\.').replace(/\//g, '\\/') + '\\?onfileload=(.*)$', 'i'),
					// tableau de tous les srcipts de l'entete HTML de la page (HEAD)
					scriptElements = document[ELTS_BY_TAGNAME]('head')[0][ELTS_BY_TAGNAME]('script'),
					// Nombre de scripts dans le tableau
					len = scriptElements.length,
					// variables de travail
					i, src, loadName;
			// Recherche de l'expression parmis tous les scripts du tableau
			for (i = 0; i < len; ++i) {
				src = scriptElements[i].src;
				if (src) {
					if (PAT_JS_ONLOAD.test(src)) {
						loadName = src.replace(PAT_JS_ONLOAD, '$1');
						delay('if(typeof(' + loadName + ')==\'function\'){' + loadName + '();}else{throw \'On file ' + jsFile + ' load - Fonction non définie : ' + loadName + ' (\'+typeof(' + loadName + ')+\')\';}', 0);
					}
				}
			}
		}

		/**
		 * Copie toutes les propriétés d'un objet dans un autre sauf si la propriété existe déjà dans l'objet de destination
		 * @param {Object} child objet de destination.
		 * @param {Object} parent objet source.
		 * @param {?boolean=} overwrite (optionel) true|1 pour préciser que l'on
		 *   veut surcharger les propriétés existantes de child.
		 */
		function toolsExtend(child, parent, overwrite) {
			if (!child) {errorMsg('tools.extend: child ' + ERR_IS_NOT_VALID);}
			if (!parent) {errorMsg('tools.extend: parent ' + ERR_IS_NOT_VALID);}
			// copie de toutes les propriétés de parent vers child
			var p;
			for (p in parent) {
				if (parent.hasOwnProperty(p) && (child[p] === UNDEFINED || overwrite || !child.hasOwnProperty(p))) {
					child[p] = parent[p];
				}
			}
			return child;
		}

		/**
		 * Copie toutes les propriétés et méthodes d'un objet
		 * dans un autre.
		 * @param {Object} destination objet de destination.
		 * @param {Object} source objet source.
		 * @param {?function(Object):boolean=} filter fonction de
		 *   filtrage qui retourne true si l'objet qui lui est
		 *   fourni est accepté, sinon false.
		 */
		function toolsCopy(destination, source, filter) {
			if (!destination) {errorMsg('tools.copy: destination object ' + ERR_IS_NOT_VALID);}
			if (!source) {errorMsg('tools.copy: source object ' + ERR_IS_NOT_VALID);}
			// copie de toutes les propriétés de source vers destination
			var property;
			for (property in source) {
				if (source.hasOwnProperty(property) && (isNotFunction(filter) || filter(source[property]))) {
					destination[property] = source[property];
				}
			}
			return destination;
		}

		/**
		 * Copie toutes les propriétés d'un objet dans un autre.
		 * Cette fonction ne copie pas les méthodes.
		 *
		 * @param {Object} destination objet de destination.
		 * @param {Object} source objet source.
		 */
		function toolsCopyProperties(destination, source) {
			return toolsCopy(destination, source, isNotFunction);
		}

		/**
		 * Copie toutes les méthodes d'un objet dans un autre.
		 * Cette fonction ne copie pas les propriétés.
		 *
		 * @param {Object} destination objet de destination.
		 * @param {Object} source objet source.
		 */
		function toolsCopyMethods(destination, source) {
			return toolsCopy(destination, source, isFunction);
		}

		/**
		 *
		 * @param {string} chaine formatée en hh:mm.
		 */
		function convertToMinute(chaine) {
			if (/^([0-9]{1,2}):([0-9]{2})$/.test(chaine)) {
				return parseInt(RegExp.$1, 10) * 60 + parseInt(RegExp.$2, 10);
			}
			errorMsg('tools.convertToMinute: Le paramètre ' + chaine + " n'est pas formaté selon hh:mm");
			return 0;
		}

		// valeur booléenne qui représente la possibilité d'utiliser les cookies sur le navigateur courant
		var cookieEnabled;

		/**
		 * Remplacement de la méthode navigator.cookieEnabled
		 * qui ne marche que sur Firefox (cf test
		 * http://fr.selfhtml.org/javascript/objets/affichage/navigator_cookie_enabled.htm)
		 * <br>
		 * Une technique de "closure" est utilisée pour encapsuler
		 * la vérification de la possibilité d'utilisation des
		 * cookies. Cette partie encapsulée crée un cookie et
		 * essaye d'y accéder pour voir s'il a vraiment été créé.
		 * <br>
		 * Une fois la vérification faite on ne conserve que la
		 * valeur obtenue pour la retourner à chaque appel à la
		 * méthode #isCookieEnabled().
		 * <br>
		 * NB : Testée sur Firefox,IE
		 *
		 * @return {boolean} true si et seulement si le navigateur courant
		 *   accepte les cookies ; sinon false.
		 */
		(function() { // tech de "closure"
			// Nom du cookie de test.
			var nameCookieTest = 'ev_testcookie',
					// Expression régulière permettant de retrouver le cookie de test.
					regexCookieTest = new RegExp('(?:; )?' + nameCookieTest + '=([^;]*);?'),
					// création d'un timestamp (nombre considéré comme aléatoire, utilisé juste pour notre test)
					ts = new Date().getTime(),
					// date d'expiration qui permet de supprimer le cookie une fois le test effectué
					dateExpiration;
			// on enregistre le test de cookie pour voir si on peut le relire
			document.cookie = nameCookieTest + '=' + ts + ';path=/';
			// on regarde si on peut lire le cookie (on vérifie que le timestamp est bien récupéré)
			cookieEnabled = regexCookieTest.test(document.cookie) && ts === parseInt(RegExp.$1, 10);
			// suppression du cookie (méthode simple : on fixe la date d'expiration pour que le cookie expire dans une seconde)
			dateExpiration = new Date();
			dateExpiration.setTime(dateExpiration.getTime() + 1000);
			document.cookie = nameCookieTest + '=;path=/;expires=' + dateExpiration.toGMTString();
		}());

		// on cree une fonction qui renverra seulement la valeur booléenne
		function isCookieEnabled() {
			return cookieEnabled;
		}

		/**
		 * retourne un tableau qui est la copie du tableau passé en paramètre.
		 * C'est une copie profonde (les éléments du tableau ne sont pas des référence).
		 *
		 * @param {!Array} tab tableau à copier.
		 * @return {!Array} tableau copié.
		 */
		function toolsClone(tab) {
			if (!tab) {errorMsg('tools#clone(): Le paramètre doit etre un tableau');}
			var output = [], i;
			for (i = 0; i < tab.length; i++) {
				output[i] = tab[i];
			}
			return output;
		}

		// regexp reconnaissant les caractère spéciaux unicode
		var regexpSpecChars = /%([C-F][0-9A-F])/g;
		function decodeUriCharacters(uri) {
			return EVAL('"' + uri.replace(regexpSpecChars, '\\x$1') + '"');
		}

		/**
		 * Récuperation d'un parametre de l'url.
		 * @param {string} _name : nom du paramètre à récupérer.
		 */
		function getParameter(_name) {
			//on recupere la query, on supprime le ? de depart, et on divise la chaine dans un tableau avec le separateur &, qui est du coup supprimé
			var pArr = location.search.substring(1).split('&'),
					i = pArr.length, idx;
			while (i) {
				--i;
				// on ne traite que si c'est le parametre cherché
				if (pArr[i].indexOf(_name) >= 0) {
					idx = pArr[i].indexOf('=') + 1;
					if (idx) { return pArr[i].substring(idx); }
				}
			}
			return NULL;
		}

		/**
		 * Formatte une date en chaine de caractère.
		 * V1 de la fonction. Un seul format de date supporté. JJ mois AAAA. Mois écrit en toute lettre en fonction de la  langue courante.
		 * Utilise les tableaux de données définis dans le fichier
		 *  => Tableau des mois, et jours en texte, en fonction des lang.
		 * FIXME à déplacer vers ev.time.util
		 *
		 * @param {Date} date Date à formatter.
		 * param {string} format Chaine de caractère donnant le format de date souhaitée. FIXME: PAS PRIS ENCORE EN COMPTE.
		 * @return {string} La chaine de caractères formatée.
		 */
		function formatDateTo(date) {
			var day = date.getDate(), // entier indiquant le jour du mois ( 1-31).
					month = date.getMonth(),// Entier indiquant le mois courant.(0-11).
					year = date.getFullYear(),// Entier indiquant l'année courante sur quatre digits.
					out = NULL;
			if (ev.lang.current === 'de_DE') { // On met un point derriere les dates dans la version allemande (17 Dezember >> 17. Dezember)
				out = day.toString() + '. ' + tabMoisTexteLong[month + 1] + ' ' + year.toString();
			}
			else {
				out = day.toString() + ' ' + tabMoisTexteLong[month + 1] + ' ' + year.toString();
			}
			return out;
		}

		///////// DEBUT ev/tools/array.js ///////////
		/**
		 * Fonction utile ajoutant les éléments du 2e tableau
		 * donné dans le premier.
		 *
		 * @param {Object} a tableau à compléter.
		 * @param {Object} b tableau à ajouter à la fin du premier.
		 */
		function arrayAppend(a, b) {
			var i, l = b.length;
			for (i = 0; i < l; ++i) {
				a.push(b[i]);
			}
			return a;
		}

		function arrayToString(a) {
			if (!a) {return '¤';}
			var l = a.length, s = l + '[', i;
			if (l) {
				for (i = 0; i < l; ++i) {
					s += '\n' + a[i];
				}
				s += '\n';
			}
			return s + ']';
		}

		function deepToString(a, s) {
			if (!a) {return '¤';}
			s = s || '';
			var l = a.length, r = s + l + ' [', i;
			if (l) {
				for (i = 0; i < l; ++i) {
					if (isArray(a[i])) {
						r += '\n' + deepToString(a[i], s + ' ');
					}
					else {
						r += '\n' + s + ' ' + a[i];
					}
				}
				r += '\n' + s;
			}
			return r + ']';
		}

		/**
		 * Méthode utile similaire au indexOf applicable
		 * sur les listes en java.
		 *
		 * TODO[poblin] élargir la méthode aux types complexes.
		 *
		 * @param {!Array.<(number|string)>} array tableau à parcourir.
		 * @param {!(number|string)} val valeur cherchée ATTENTION doit être de type simple (integer, string).
		 * @return {!number} le 1er index de array contenant objet, sinon -1.
		 */
		function arrayIndexOf(array, val) {
			//on parcourt le tableau et on retourne le 1er index contenant l'objet, sinon -1
			if (!isArray(array)) {
				warnMsg('tools.array#indexOf - array doit être un tableau. [array: ' + array + ' ; looking for val: ' + val + ']');
				return -1;
			}
			if (!array.length) { return -1; }
			if (!isString(val) && !isNumber(val)) {
				errorMsg('tools.array#indexOf - val doit être de type string ou number');
				return -1;
			}
			var l = array.length;
			while (l) {
				--l;
				if (array[l] === val) { return l; }
			}
			return -1;
		}

		// déclaration du namespace ev.tools
		ev.tools = {
			onFileLoad: onFileLoad,
			extend: toolsExtend,
			copyProperties: toolsCopyProperties,
			copyMethods: toolsCopyMethods,
			copy: toolsCopy,
			convertToMinute: convertToMinute,
			isCookieEnabled: isCookieEnabled,
			clone: toolsClone,
			decodeURI: decodeUriCharacters,
			getParameter: getParameter,
			formatDateTo: formatDateTo,
			array: {
				is: isArray,
				append: arrayAppend,
				toString: arrayToString,
				deepToString: deepToString,
				indexOf: arrayIndexOf
			}
		};



		///////// DEBUT ev/dom/utils.js ///////////
		/**
		 * La méthode ev.dom.element(String/Element) retrouve un ou plusieurs éléments
		 * dans le DOM. Cette méthode retourne autant d'éléments que de
		 * de paramètre reçus.
		 * Si un paramètre est un élément DOM, il est retourné directement.
		 * Si un paramètre est une chaîne, l'élément DOM correspondant sera
		 * recherché.
		 * NOTA : cette méthode a un résultat quasi identique à la méthode $(elt,...)
		 * de Prototype. Notre méthode est itérative plutôt que recursive, mais arrive
		 * au même résultat.
		 * @param {?(string|Element)=} element id du premier élément à retrouver dans le document DOM.
		 * @return {?Element} si 1 paramètre : un élément DOM ; si plus d'1 paramètre : un tableau d'éléments DOM (Array.<Element>)
		 *   (chaque élément DOM retourné peut-être null ou UNDEFINED si non trouvé).
		 */
		function domElement(element) {
			var args = arguments, size = args.length, i;
			if (isString(element)) {
				element = document.getElementById(element);
			}
			// si on fourni un seul paramètre ou aucun, on ne passera pas ici
			// (dans ce cas, le résultat peut être un élément DOM, null ou UNDEFINED)
			if (size > 1) {
				// si on fourni plusieurs paramètres, on se prépare à retourner un tableau d'éléments DOM
				// (dont le premier est celui déjà récupéré)
				element = [element];
				for (i = 1; i < size; ++i) {
					if (isString(args[i])) {
						// si le paramètre est une chaîne, on cherche l'élément DOM correspondant
						// (UNDEFINED ou null si non trouvé), et on l'insère dans le tableau
						element.push(document.getElementById(args[i]));
					}
					else {
						// sinon, on insère le paramètre lui-même
						element.push(args[i]);
					}
				}
			}
			// Enfin, on retourne la valeur de element (l'élément de départ seul ou le tableau d'éléments DOM)
			return element;
		}

		/**
		 * Méthode permettant de créer un élément DOM.
		 * L'élément sera créé en XHTML si possible.
		 * @param {string} _tagName : type de balise à créer.
		 */
		function createElement(_tagName) {
			if (document[CREATE_ELEMENT_NS]) {
				return document[CREATE_ELEMENT_NS](W3_XHTML, _tagName);
			}
			return document[CREATE_ELEMENT](_tagName);
		}

		/**
		 * Méthode permettant d'insérer une node
		 * avant une autre dans l'arbre DOM.
		 *
		 * @param {!Element} _newNode noeud à insérer.
		 * @param {!Element} _reference noeud avant lequel il faut l'insérer.
		 */
		function insertAfter(_newNode, _reference) {
			var pn = _reference.parentNode;
			if (_reference.nextSibling) {
				pn.insertBefore(_newNode, _reference.nextSibling);
			}else {
				pn.appendChild(_newNode);
			}
		}

		/**
		 * Méthode permettant de créer un noeud texte.
		 *
		 * @param {!string} txt contenu du noeud texte.
		 */
		function createText(txt) {
			return document.createTextNode(txt);
		}

		/**
		 * Recherche les éléments DOM ayant le nom donné.
		 * @param {!string} tagName nom des tags à retrouver dans l'élément donné (par défaut le document racine).
		 * @param {?(string|Element)=} elt élément DOM dans lequel il faut effectuer la recherche, par défaut 'document' (optionel).
		 * @return {Array.<Element>} un tableau d'éléments DOM ayant le nom recherché.
		 */
		function findTags(tagName, elt) {
			return (domElement(elt) || document)[ELTS_BY_TAGNAME](tagName);
		}

		/**
		 * fct permettant d'obtenir les enfants (les enfants sont du même type) d'un noeud
		 * @param {(string|Element)} e : element contenant les enfants.
		 * @param {!string} nodeName : type des enfants a recuperer.
		 * @return {!Arary.<Element>} un tableau contenant les enfants, tableau vide s'il n'y en a pas.
		 */
		function domChildren(e, nodeName) {
			e = domElement(e);
			if (!e) {
				return [];
			}
			var childNodes = [], i, l = e.childNodes.length;
			for (i = 0; i < l; i++) {
				if (e.childNodes[i].nodeName === nodeName) {
					childNodes.push(e.childNodes[i]);
				}
			}
			return childNodes;
		}

		/**
		 * @param {Object} elt élément DOM (ou id de l'élément) duquel on cherche le suivant.
		 * @return {Element|undefined} l'élément DOM suivant l'élément donné (ou undefined si non trouvé).
		 */
		function nextElement(elt) {
			do {
				elt = elt.nextSibling;
			} while (elt && elt.nodeType !== 1);
			return elt;
		}

		/**
		 * Fonction qui indique si le className d'un élément donné
		 * contient la classe passée en paramètre.
		 * @param {Element} e : Element DOM à vérifier.
		 * @param {string} _className : nom de la classe recherhée.
		 */
		function hasClass(e, _className) {
			e = domElement(e);
			// Locate the class name (allows for mutliple class names)
			if (e && new RegExp('(^|\\s)' + _className + '(\\s|$)').test(e.className)) {
				return TRUE;
			}
			return FALSE;
		}

		/**
		 * Ajoute une classe dans l'attribut class d'un élément
		 * @param {string|Element} e Element DOM (ou id d'élément).
		 * @param {?string} _classToAdd nom de la classe à ajouter.
		 **/
		function addClass(e, _classToAdd) {
			e = domElement(e);
			if (!e) { return; }
			var classNameValue = e.className;
			if (classNameValue) {
				var classList = classNameValue.split(/\s+/),
						n = classList.length;
				while (n) {
					--n;
					if (classList[n] === _classToAdd) {
						return;
					}
				}
				e.className = (classNameValue + ' ' + _classToAdd).trim();
			}else {
				e.className = _classToAdd.trim();
			}
		}

		/**
		 * Dans une classe contenant "xxx" ou "yyy xxx zzz" on
		 * peut supprimer "xxx" pour obtenir respectivement ""
		 * ou "yyy zzz".
		 * @param {string|Element} e Element DOM (ou id d'élément).
		 * @param {?string} cls nom de la classe à enlever.
		 **/
		function removeClass(e, cls) {
			e = domElement(e);
			if (!e) { return; }
			var classNameValue = e.className;
			if (classNameValue) {
				var classList = classNameValue.split(/\s+/),
						newClassName = '', i;
				for (i = 0; i < classList.length; i++) {
					if (classList[i] !== cls) {
						newClassName += classList[i] + ' ';
					}
				}
				e.className = newClassName.trim();
			}
		}

		/**
		 * Effectue la permutation des classes passées en paramètre (class1 et class2) pour
		 * l'élément lui passé en paramètre (e)
		 * Les classes permutées sont rejetées à la fin de l'attribut class de l'élément.
		 * EXEMPLE:
		 * <tag class="xxx yyy">
		 * après l'invocation de swapClasses(tagElement,"xxx","zzz"), devient:
		 * <tag class="yyy zzz">
		 *
		 * @param {!(string|Element)} elt Element DOM (ou son id) sur lequel on doit échanger les classes de style.
		 * @param {!string} class1 nom de la 1ère classe de style de l'échange.
		 * @param {!string} class2 nom de la 2e classe de style de l'échange.
		 **/
		function swapClasses(elt, class1, class2) {
			elt = domElement(elt);
			if (!elt) {return;}
			if (hasClass(elt, class1)) {
				removeClass(elt, class1);
				addClass(elt, class2);
			}
			else if (hasClass(elt, class2)) {
				removeClass(elt, class2);
				addClass(elt, class1);
			}
		}

		/**
		 * Recherche dans le conteneur donné, les éléments DOM ayant
		 * la classe demandée, le  le type donné.
		 *
		 * @param {!string} cls nom de la classe d'élément à retrouver dans le document DOM.
		 * @param {?(string|Element)=} elt élément conteneur (ou son id) dans lequel il faut effectuer la recherche ( ex : document ).
		 * @param {string=} tagName type d'élément DOM (ex: li, span, ...) (optionel).
		 * @return {!Array.<Element>} un tableau d'éléments DOM ayant la classe CSS recherchée (vide si rien trouvé).
		 */
		function elementsWithClassIn(cls, elt, tagName) {
			var resultats = [],
					// Locate the class name (allows for mutliple class names)
					RE = new RegExp('(^|\\s)' + cls + '(\\s|$)'),
					// Limit search by type, or look through all elements
					elements = findTags(tagName || '*', elt),
					// variable de travail
					i, n = elements.length;
			for (i = 0; i < n; ++i) {
				if (RE.test(elements[i].className)) {
					// If the element has the class, add it for return
					resultats.push(elements[i]);
				}
			}
			// Return the list of matched elements
			return resultats;
		}

		/**
		 * Recherche dans tout le document, les éléments DOM ayant la
		 * classe demandée, et le cas échéant le type donné.
		 *
		 * @param {!string} cls nom de la classe d'élément à retrouver dans le document DOM.
		 * @param {string=} tagName (optionnel) type d'élément DOM (ex: li, span, ...), par défaut tout type d'élément (optionel).
		 * @param {?(string|Element)=} elt (optionel) élément DOM dans lequel il faut effectuer la recherche, par défaut 'document'.
		 * @return {Array.<Element>} un tableau d'éléments DOM ayant la classe CSS recherchée.
		 */
		function elementsWithClass(cls, tagName, elt) {
			return elementsWithClassIn(cls, elt, tagName);
			// FIXME à virer de là
			//    var resultats=[];
			//    // Locate the class name (allows for mutliple class names)
			//    var RE=new RegExp("(^|\\s)"+cls+"(\\s|$)");
			//    // Limit search by type, or look through all elements
			//    var elements=findTags(tagName||"*");
			//    for(var i=0; i<elements.length; ++i){
			//      if(RE.test(elements[i].className)){
			//        // If the element has the class, add it for return
			//        resultats.push(elements[i]);
			//      }
			//    }
			//    // Return the list of matched elements
			//    return resultats;
		}

		/**
		 * Récupère le texte contenu dans le(s) élément(s) donné(s).
		 *
		 * @param {!(string|Element)} elt élément DOM ou son id (ou tableau d'éléments DOM ; FIXME : ajouter "|Array.<Element>").
		 * @return {!string} le texte contenu dans le(s) élément(s) DOM donné(s).
		 */
		function getText(elt) {
			elt = domElement(elt); // FIXME marche pas si elt est un tableau
			var t = '', j, l;
			// If an element was passed, get it's children,
			// otherwise assume it's an array
			elt = elt.childNodes || elt;
			// Look through all child nodes
			for (j = 0, l = elt.length; j < l; j++) {
				if (elt[j].nodeType !== 1) {
					// If it's not an element, append its text value
					t += elt[j].nodeValue;
				}
				else {
					// Otherwise, recurse through all the element's children
					t += getText(elt[j].childNodes);
				}
			}
			// Return the matched text
			return t;
		}

		/**
		 * Méthode de nettoiement du conteneur donné.
		 *
		 * @param {!(Element|string)} elt un conteneur DOM à vider (ou son id).
		 */
		function domClear(elt) {
			elt = domElement(elt);
			if (!elt) {
				return;
			}

			// On ne peut supprimer tous les éléments du conteneur autrement que un à un.
			while (elt.firstChild) {
				elt.removeChild(elt.firstChild);
			}
		}
		// On s'assure que le namespace ev.dom existe
		if (!ev.dom) { ev.dom = {}; }
		// et on y ajoute les nouvelles méthodes
		toolsCopy(ev.dom, {
			element: domElement,
			create: createElement,
			insertAfter: insertAfter,
			createText: createText,
			tags: findTags,
			children: domChildren,
			nextElement: nextElement,
			hasClass: hasClass,
			addClass: addClass,
			removeClass: removeClass,
			swapClasses: swapClasses,
			elementsWithClassIn: elementsWithClassIn,
			elementsWithClass: elementsWithClass,
			text: getText,
			clear: domClear
		});
		///////// FIN ev/dom/utils.js /////////////

		// et on ajoute les nouvelles méthodes de paramètres aux ev.tools
		toolsCopy(ev.tools, paramTools);
		/**
		 * Fonction permettant de récupérer les paramètres fournis sur
		 * un appel d'un fichier js donné.<br>
		 * Le premier paramètre est optionel et permet simplement de
		 * préciser si besoin le nom du tag contenant le script
		 * concerné. Cela permet, comme cela est souvent le cas d'avoir
		 * la possibilité de traiter les fichiers étant en fin de page
		 * (dans le tag body).
		 * NOTA : cette méthode ne gère pas de cache, à chaque appel
		 * elle refait le traitement.
		 *
		 * @param {String} pt [optionel] : tag parent [par défaut: head].
		 * @param {String} jsf : nom du fichier js.
		 */
		ev.tools.getJsParameters = function(pt, jsf) {
			if (!jsf) {jsf = pt;pt = 'head';}
			/** regexp */
			var RE_JS_PARAMS = new RegExp(jsf.replace(/\./g, '\\.') + '\\?([a-zA-Z0-9_:\\./=&]*)$'),
					/** script nodes */
					scripts = findTags('script', findTags(pt)[0]), i = scripts.length, src, params;
			while (i) {
				--i;
				src = scripts[i].src;
				//debugMsg('ev.tools#getJsParameters(): looking for file '+jsf+': '+src);
				/*
				if(!(src&&RE_JS_PARAMS.test(src))){
					continue;
				}
				params=RegExp.$1;
				return createSimpleMap('=', params.split(RE_PARAM_SEPARATOR));
				 */
				if ((src && RE_JS_PARAMS.test(src))) {
					params = RegExp.$1;
					return createSimpleMap('=', params.split(RE_PARAM_SEPARATOR));
				}
			}
			return {};
		};

		/**
		 * Retourne le texte court correspondant au quantième d'un jour de la semaine. Le quantième est celui utilisé dans la
		 * classe date 0=dimanche, 1=lundi, 2=mardi, 3=mercredi, 4=jeudi, 5=samedi
		 **/
		window.getJourTexteCourt = function(value) {
			return tabJoursTexteCourt[Math.abs(value)];
		};

		/**
		 * Retourne le texte long correspondant au quantième d'un jour de la semaine. Le quantième est celui utilisé dans la
		 * classe date 0=dimanche, 1=lundi, 2=mardi, 3=mercredi, 4=jeudi, 5=samedi
		 **/
		window.getJourTexteLong = function(value) {
			return tabJoursTexteLong[Math.abs(value)];
		};

		/**
		 * Retourne le texte court correspondant au quantième d'un mois. Contrairement à ce qui est défini dans la classe date
		 * le compte ne débute pas à 0, mais il associe 1=janvier, 2=février, 3=mars, 4=avril, etc...
		 **/
		window.getMoisTexteCourt = function(value) {
			return tabMoisTexteCourt[Math.abs(value)];
		};

		/**
		 * Retourne le texte long correspondant au quantième d'un mois. Contrairement à ce qui est défini dans la classe date
		 * le compte ne débute pas à 0, mais il associe 1=janvier, 2=février, 3=mars, 4=avril, etc...
		 **/
		window.getMoisTexteLong = function(value) {
			return tabMoisTexteLong[Math.abs(value)];
		};

		/**
		 * Permet de créer une date à partir de l'année, le mois
		 * et le jour sans risque de création de dates erronées.
		 *
		 * Cela évite de se retrouver avec une date imprévue
		 * lorsque la date du jour comporte des irrégularités.
		 * (ex: 31 du mois, mois de février, année bissextile)
		 *
		 * L'objet Date se décalant sur une date correcte dés
		 * qu'un élément entré est incorrect, on peut souvent
		 * avoir ce problème de date imprévue. (ex: une date du
		 * jour étant 14/02/2008, lorsqu'on veut initialiser le
		 * 31/05/2008, si on commence par changer le jour, la
		 * date du 31/02/2008 étant erronée, on bascule
		 * directement sur le 02/03/2008, ensuite le mois vient
		 * et on obtient 02/05/2008 qui n'est pas la date
		 * souhaitée.
		 *
		 * Si la date obtenue après nos manipulations est
		 * différente des paramètres, là on sait que c'est
		 * une mauvaise date.
		 *
		 * @param {number} _j : jour de la date (1-31).
		 * @param {number} _m : mois de la date (1-12).
		 * @param {number} _a : année de la date (en 4 chiffres).
		 */
		function createDate(_j, _m, _a) {
			// Les deux lignes suivantes convertissent la chaine jour en nombre
			_j--;
			_j++;
			var d = new Date();
			// On force d'abord à Janvier (pour être dans un mois de 31 jours)
			d.setMonth(0);
			// on supprime toutes les données sur l'heure
			d.setHours(0);
			d.setMinutes(0);
			d.setSeconds(0);
			d.setMilliseconds(0);
			// On positionnera ensuite dans l'ordre l'année, le jour et le mois
			d.setFullYear(_a);
			d.setDate(_j);
			d.setMonth(_m - 1);
			return d;
		}

		/**
		 * Indique si la date définie par les quantièmes jour mois an est une date
		 * valide. Retourne le booléen true si la date est valide et postérieure
		 * à maintenant, false sinon.
		 *
		 * @param {number} _j : jour de la date (1-31).
		 * @param {number} _m : mois de la date (1-12).
		 * @param {number} _a : année de la date (en 4 chiffres).
		 */
		window.isValidDate = function(_j, _m, _a) {
			var now = new Date();
			var d = createDate(_j, _m, _a);
			_j = parseInt(_j, 10);
			_m = parseInt(_m, 10) - 1;
			_a = parseInt(_a, 10);
			// alert('jour = '+_j+' - '+d.getDate()+'\n'
			// +'mois = '+_m+' - '+d.getMonth()+'\n'
			// +'annee = '+_a+' - '+d.getFullYear());
			if (_j !== d.getDate()) {return FALSE;}
			if (_m !== d.getMonth()) {return FALSE;}
			if (_a !== d.getFullYear()) {return FALSE;}
			if (now.getTime() > d.getTime()) {return FALSE;}
			return TRUE;
		};

		/**
		 * Dans les sélects générés automatiquement, il y a un tag <option> vide (obligatoire pour la validation XHTML). Avant
		 * l'insertion des options calculées, il faut supprimer cette option vide. C'est le rôle de cette fonction
		 *  - s: l'élément select dans lequel on doit supprimet l'option vide
		 */
		function removeOptionInSelect(s) {
			var n = s.firstChild;
			while (n) {
				if (n.tagName === 'OPTION') {
					s.remove(n);
					return;
				}
				n = n.nextSibling;
			}
		}

		/**
		 * Retourne un objet option
		 *  - v value de l'option
		 *  - t texte de l'option
		 **/
		function createOption(v, t) {
			var o = document[CREATE_ELEMENT]('OPTION');
			o.value = v;
			o.text = t;
			return o;
		}

		/**
		 * Ajoute une option dans un select à la suite des options précédents
		 *  - s: l'élément select dans lequel on insére l'option
		 *  - o: l'élément option à insérer
		 **/
		function addOptionInSelect(s, o) {
			var i = s.length;
			try {
				s.add(o, NULL);
			}catch (e1) {
				try {
					s.add(o, i);
				}catch (e2) {}
			}
		}

		/**
		 * Initialise automatiquement le selecteur d'heures avec toutes ses valeurs
		 *  - s: l'élément select à modifier
		 **/
		function initHeures(selectElement) {
			//    debugMsg('initHeures> '+selectElement.id);
			removeOptionInSelect(selectElement);
			var heure, value = '';
			for (heure = 0; heure < 24; ++heure) {
				if (heure < 10) {
					value = '0' + heure;
				}
				else {
					value = heure;
				}
				value += ':00';
				addOptionInSelect(selectElement, createOption(value, value));
			}
		}

		/**
		 * Initialise automatiquement le selecteur de jours avec toutes ses valeurs, précision l'élément SELECT doit être structuré comme suit:
		 * <select attributs... ><option></option></select>
		 * Si on renseigne defaultTextJours et defaultValueJours dans l'appel de la fonction, un élément option supplémentaire est ajouté au début de la liste d'options. NOTA: Par défaut HTML affiche toujours le premier élément d'une liste select, pour éventuellement modifier, la valeur par défaut on fera appel à des fonctions JS annexes.
		 * - selectElement: l'élément select à modifier
		 * - defaultTextJours (optionel): un texte éventuel précédant la liste des jours.
		 * - defaultValueJours (optionel): la valeur associée au texte précédent. NOTA: Si l'un ou l'autre des deux paramètres defaultTextJours ou defaultValueJours est
		 * manquant, aucune option supplémentaire n'est alors affichée en début de liste.
		 **/
		function initJours(selectElement, defaultTextJours, defaultValueJours) {
			//    debugMsg('initJours> '+selectElement.id);
			removeOptionInSelect(selectElement);
			if (defaultTextJours !== UNDEFINED && defaultValueJours !== UNDEFINED) {
				addOptionInSelect(selectElement, createOption(defaultValueJours, defaultTextJours));
			}
			var jour, value;
			for (jour = 1; jour <= 31; ++jour) {
				if (jour < 10) {
					value = '0' + jour;
				}else {
					value = jour;
				}
				addOptionInSelect(selectElement, createOption(value, jour));
			}
		}

		/**
		 * Initialise automatiquement le selecteur de mois avec toutes ses valeurs, et définit comme mois par défaut
		 * celui défini sur l'ordinateur client
		 *  - selectElement: l'élément select à modifier
		 **/
		function initMois(selectElement) {
			//    debugMsg('initMois> '+selectElement.id);
			removeOptionInSelect(selectElement);
			var now = new Date(), month = now.getMonth() + 1, year = now.getFullYear(), jour;
			for (jour = 0; jour <= 12; ++jour) {
				// FIXME utiliser ev.lang.getMoisTexteCourt(mois) des qu'elle est dispo
				addOptionInSelect(selectElement, createOption((month < 10 ? '0' : '') + month + '/' + year, tabMoisTexteCourt[month] + ' ' + year));
				++month;
				if (month > 12) {
					month = 1;
					++year;
				}
			}
		}

		/**
		 * Effectue la permutation des classes folded et unfolded sur l'élément passé en paramètre.
		 **/
		function foldUnfold(e) {
			swapClasses(e, 'folded', 'unfolded');
		}

		/**
		 * Rend un champ input (ou select) inactif et modifie sa classe CSS
		 * Fonction utilisée par les scripts formXXX.js
		 *  - e: element champ à rendre inactif
		 **/
		function disableInput(e) {
			e = domElement(e);
			if (!e) {return;}
			e.disabled = 'disabled';
			removeClass(e, 'enabled');
			addClass(e, 'disabled');
		}

		/**
		 * Rend un champ input (ou select) actif et modifie sa classe CSS
		 * Fonction utilisée par les scripts formXXX.js
		 *  - e: element champ à rendre actif
		 **/
		function enableInput(e) {
			e = domElement(e);
			if (!e) {return;}
			e.disabled = '';
			removeClass(e, 'disabled');
			addClass(e, 'enabled');
		}

		/**
		 * Définit l'opacité d'un élément
		 * fonction utilisée par toggle()
		 *  - e: l'élément dont on doit définir l'opacité
		 *  - opacity: opacité donnée à l'élément
		 **/
		function setOpacity(e, opacity) {
			e = domElement(e);
			if (!e) {return;}
			if (!e.style) {return;}
			e.style.opacity = opacity;
			e.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
		}

		// Gestion des OS et Navigateurs
		// for use with Navigator + Os
		var UNKNOWN = 0,
				// for use with Navigator
				FIREFOX = 1,
				MSIE = 2,
				OPERA = 3,
				SAFARI = 4,
				NETSCAPE = 5,
				CAMINO = 6,
				KONQUEROR = 7,
				CHROME = 8,
				FLOCK = 9,
				// for use with Os
				WINDOWS_XP = 1,
				WINDOWS_VISTA = 2,
				WINDOWS_2000 = 3,
				WINDOWS_SERVER_2003 = 4,
				WINDOWS_98 = 5,
				MAC_OS_X_PPC = 6,
				MAC_OS_X_INTEL = 7,
				LINUX = 8,
				// current navigator
				genericNavigator;

		/**
		 * Crée un objet navigateur permettant d'avoir une version
		 * déjà triée des informations sur un navigateur.
		 *
		 * @param {!string} ua userAgent de la fenetre du navigateur à identifier.
		 * @constructor
		 */
		function Navigateur(ua) {
			// Initialisation des propriétés
			var id = UNKNOWN,
					version = 0.0,
					// variable temporaire
					debut;

			// Firefox
			if (ua.indexOf('firefox/') >= 0) {
				id = FIREFOX;
				debut = ua.indexOf('firefox/');
				version = ua.substring(debut + 8).match(/[0-9]+\.[0-9]+/);
			}
			// MSIE
			else if ((debut = ua.indexOf('msie')) >= 0) {
				id = MSIE;
				version = ua.substring(debut + 4).match(/[0-9]+\.[0-9]+/);
				if (version < 8 && ua.indexOf('trident/') >= 0) {
					version = '8.0';
				}
			}
			// Opera
			else if (ua.indexOf('opera') >= 0) {
				id = OPERA;
				debut = ua.indexOf('opera');
				version = ua.substring(debut + 6).match(/[0-9]+\.[0-9]+/);
			}
			// Chrome
			else if ((debut = ua.indexOf('flock')) >= 0) {
				id = FLOCK;
				version = ua.substring(debut + 6).match(/[0-9]+\.[0-9]+/);
			}
			// Chrome
			else if ((debut = ua.indexOf('chrome')) >= 0) {
				id = CHROME;
				version = ua.substring(debut + 7).match(/[0-9]+\.[0-9]+/);
			}
			// Safari
			else if (ua.indexOf('safari') >= 0) {
				id = SAFARI;
				debut = ua.indexOf('version');
				version = ua.substring(debut + 8).match(/[0-9]+\.[0-9]+/);
			}
			// Netscape
			else if (ua.indexOf('netscape') >= 0) {
				id = NETSCAPE;
				debut = ua.indexOf('netscape');
				version = ua.substring(debut + 9).match(/[0-9]+\.[0-9]+/);
			}
			// Camino
			else if (ua.indexOf('camino/') >= 0) {
				id = CAMINO;
				debut = ua.indexOf('camino/');
				version = ua.substring(debut + 7).match(/[0-9]+\.[0-9]+/);
			}
			// Konqueror
			else if (ua.indexOf('konqueror/') >= 0) {
				id = KONQUEROR;
				debut = ua.indexOf('konqueror/');
				version = ua.substring(debut + 10).match(/[0-9]+\.[0-9]+/);
			}
			this.id = id;
			this.version = version;
		}

		/**
		 * Crée un objet Os permettant d'avoir une version déjà triée
		 * des informations sur un système d'exploitation.
		 *
		 * @param {!string} ua user Agent de la fenetre du navigateur courant.
		 * @constructor
		 */
		function Os(ua) {
			// Initialisation des propriétés
			var os = UNKNOWN,
					//variables temporaires
					tab_elt,
					tab_os,
					system,
					RE_OS = /^[^\(]*\(([^\)]+)\).*/;

			if (ua.indexOf('opera') >= 0) {
				//tab_elt = ua.replace(/^[^\(]*\(([^\)]+)\).*/,"$1").split(";");
				//os=tab_elt[0];
				tab_elt = ua.split('(');
				tab_os = tab_elt[1].split(';');
				//os= =tab_os[0];
				system = tab_os[0];
			}
			else if (ua.indexOf('firefox/4') >= 0) {
				tab_elt = ua.replace(RE_OS, '$1').split(';');
				system = tab_elt[0];
			}
			else if (ua.indexOf('chrome/11') >= 0) {
				tab_elt = ua.replace(RE_OS, '$1').split(';');
				system = tab_elt[1];
			}
			else {
				tab_elt = ua.replace(RE_OS, '$1').split(';');
				//tab_elt = ua.split(";");
				//os=tab_elt[2];
				system = tab_elt[2];
			}

			//system = system.replace(/^\s+/, "").replace(/\s+$/, "");
			system = (system && system.trim()) || 'unknown';
			//alert(CORE_PFX + 'Os(): system = "'+system+'"');

			if (system === 'windows nt 5.1') {
				os = WINDOWS_XP;
			}
			else if (system === 'windows nt 6.0') {
				os = WINDOWS_VISTA;
			}
			else if (system === 'windows nt 5.0') {
				os = WINDOWS_2000;
			}
			else if (system === 'windows nt 5.2') {
				os = WINDOWS_SERVER_2003;
			}
			else if (system === 'windows 98') {
				os = WINDOWS_98;
			}
			else if (system === 'ppc mac os x mach-o' || system === 'ppc mac os x' || system === 'macintosh') {
				os = MAC_OS_X_PPC;
			}
			else if (system.indexOf('intel mac os x') >= 0) {
				os = MAC_OS_X_INTEL;
			}
			else if (system === 'linux') {
				os = LINUX;
			}
			this.os = os;
		}

		/**
		 * Cette classe définit le navigateur de la fenetre courante
		 * via les deux classes définies plus haut.
		 *
		 * @constructor
		 **/
		function GenericNavigator() {
			var navigator = new Navigateur(userAgent),
					version = navigator.version,
					os = new Os(userAgent);

			this.getNameNavigator = function() {
				switch (navigator.id) {
					case FIREFOX: return 'Firefox ' + version;
					case MSIE: return 'Microsoft Internet Explorer ' + version;
					case OPERA: return 'Opera ' + version;
					case SAFARI: return 'Safari ' + version;
					case NETSCAPE: return 'Netscape ' + version;
					case CAMINO: return 'Camino ' + version;
					case KONQUEROR: return 'Konqueror ' + version;
					case CHROME: return 'Chrome ' + version;
					case FLOCK: return 'Flock ' + version;
					default : return 'Inconnu';
				}
			};

			this.getNameOs = function() {
				switch (os.os) {
					case WINDOWS_XP: return 'WINDOWS XP';
					case WINDOWS_VISTA: return 'WINDOWS VISTA';
					case WINDOWS_2000: return 'WINDOWS 2000 ';
					case WINDOWS_SERVER_2003: return 'WINDOWS SERVER 2003 ';
					case WINDOWS_98: return 'WINDOWS 98 ';
					case MAC_OS_X_PPC: return 'MAC OS X PPC ';
					case MAC_OS_X_INTEL: return 'MAC OS X INTEL ';
					case LINUX: return 'LINUX ';
					default: return 'Inconnu';
				}
			};

			this.getXMLHttpRequest = function() {
				if (navigator.id === MSIE && version < 7) {// MSIE version <7
					try {
						return new window.ActiveXObject('Msxml2.XMLHTTP');
					}
					catch (e1) {
						try {
							return new window.ActiveXObject('Microsoft.XMLHTTP');
						}
						catch (e2) {
						}
					}
				}
				else {
					var xmlHttpRequest = new window.XMLHttpRequest();
					// Évite un bug du navigateur Safari :
					if (xmlHttpRequest.overrideMimeType) {
						xmlHttpRequest.overrideMimeType('text/xml');
					}
					return xmlHttpRequest;
				}
				throw 'GenericNavigator.getXMLHttpRequest(): could not retreive an XMLHttpRequest instance';
			};

			this.navigator = navigator;
			this.os = os;
		}
		genericNavigator = new GenericNavigator();

		// on place des accès aux fonctions précédentes dans window
		toolsCopy(window, {
			submitValueSubmitME: submitValueSubmitME,
			waitValueSubmitME: waitValueSubmitME,
			getParameter: getParameter,
			// Raccourcis vers les DOM utils
			addClass: addClass,
			removeClass: removeClass,
			hasClass: hasClass,
			swapClasses: swapClasses,
			foldUnfold: foldUnfold,
			disableInput: disableInput,
			enableInput: enableInput,
			setOpacity: setOpacity,
			// <select ><option ...>...</select>
			removeOptionInSelect: removeOptionInSelect,
			createOption: createOption,
			addOptionInSelect: addOptionInSelect,
			// Time managing
			createDate: createDate,
			initHeures: initHeures,
			initJours: initJours,
			initMois: initMois,
			// for use with Navigator + Os
			UNKNOWN: UNKNOWN,
			// for use with Navigator
			FIREFOX: FIREFOX,
			MSIE: MSIE,
			OPERA: OPERA,
			SAFARI: SAFARI,
			NETSCAPE: NETSCAPE,
			CAMINO: CAMINO,
			KONQUEROR: KONQUEROR,
			CHROME: CHROME,
			FLOCK: FLOCK,
			// for use with Os
			WINDOWS_XP: WINDOWS_XP,
			WINDOWS_VISTA: WINDOWS_VISTA,
			WINDOWS_2000: WINDOWS_2000,
			WINDOWS_SERVER_2003: WINDOWS_SERVER_2003,
			WINDOWS_98: WINDOWS_98,
			MAC_OS_X_PPC: MAC_OS_X_PPC,
			MAC_OS_X_INTEL: MAC_OS_X_INTEL,
			LINUX: LINUX,
			// Generic OS & Navigator
			Navigator: Navigateur,
			Os: Os,
			GenericNavigator: GenericNavigator,
			genericNavigator: genericNavigator
		});

		/**
		 * Implémentation de base du module b64 (Base 64 Codec).<br>
		 * Par défaut ce sont des méthodes qui ne font rien. Elles
		 * sont surchargées plus bas pour certaines versions d'IE.
		 */
		ev.b64 = {
			/**
				 * Retourne le code donné car si ce n'est pas IE (<8),
				 * l'image sera bien gérée !
				 *
				 * @param {!string} codeB64 code base64 de l'image à décoder.
				 * @return {!string} le paramètre qui a été donné.
				 */
			ie7FixSrc: function(codeB64) { return codeB64; },

			/**
				 * Fonction qui ne fait rien.
				 */
			ie7FixImg: function() {},

			/**
				 * Fonction qui ne fait rien.
				 */
			ie7FixAll: function() {}
		};

		/**
		 * Fonction interne de modification d'un évenement JS.
		 * Sera surchargée plus bas, si on est sur IE (<8).
		 *
		 * @param {!Element} attribute element Attribut correspondant à l'événement à mettre à jour.
		 * @param {!string} newJavascript chaine de caractères représentant le code JS à exécuter sur l'événement.
		 */
		var innerAttributeUpdate = function(attribute, newJavascript) {
			attribute.nodeValue = newJavascript;
		};

		if (genericNavigator.navigator.id === MSIE) {
			debugMsg(CORE_PFX + INIT_METHOD_NAME + ': Navigateur MISE -> Initializing event bubbling stop method...');

			/**
			 * Cette méthode permet de stopper l'évènement en cours sur IE
			 * (Sur les autres navigateurs il suffit de retourner la valeur false)
			 */
			window.cancelIEEvent = function() {
				if (window.event) {
					// Traitement spécifique pour le blocage de l'évènement sur IE (pour qu'il ne remonte pas au navigateur)
					window.event.cancelBubble = TRUE;
					window.event.returnValue = FALSE;
				}
			};

			if (!document.documentMode || document.documentMode < 8) {
				debugMsg(CORE_PFX + INIT_METHOD_NAME + ': Navigateur MISE doc mode ' + document.documentMode + ' < 8 -> Redefining event updating method...');

				/**
				 * Fonction interne de modification d'un évenement JS.
				 * Fonction surchargée pour IE (<8).
				 *
				 * @param {!Element} attribute element Attribut correspondant à l'événement à mettre à jour.
				 * @param {!string} newJavascript chaine de caractères représentant le code JS à exécuter sur l'événement.
				 */
				innerAttributeUpdate = function(attribute, newJavascript) {
					attribute.nodeValue = function() {
						EVAL(newJavascript);
					};
				};
			}

			if (genericNavigator.navigator.version < 8) {
				debugMsg(CORE_PFX + INIT_METHOD_NAME + ': Navigateur MISE Version ' + genericNavigator.navigator.version + ' < 8 -> Initializing Base64 patch methods...');

				// a regular expression to test for Base64 data
				var BASE64_DATA = /^data:image\/(jpe?g|gif|png);base64,(.*)$/i,
						BASE64_PATH = PATH.era + '/image/base64/';

				/**
				 * Permet d'interroger l'ERA pour récupérer l'image
				 * binaire pour IE7 (qui ne supporte pas les images
				 * encodées en Base64) à partir de son code base64.
				 *
				 * @param {!string} codeB64 code base64 de l'image à décoder.
				 * @return {!string} l'adresse qui va permettre de télécharger
				 *   l'image en binaire.
				 */
				ev.b64.ie7FixSrc = function(codeB64) {
					if (BASE64_DATA.test(codeB64)) {
						codeB64 = BASE64_PATH + RegExp.$2 + '.' + RegExp.$1;
						debugMsg('b64#ie7FixSrc(): décodage d\'une image codée en Base64 (<=IE7) -> src = ' + codeB64);
						return codeB64;
					}
					return codeB64;
				};

				/**
				 * Permet d'interroger l'ERA pour récupérer l'image
				 * binaire pour IE7 (qui ne supporte pas les images
				 * encodées en Base64) à partir de son code base64.<br>
				 * Cette fonction prend en paramètre un objet DOM
				 * image pour corriger son attribut 'src'.
				 *
				 * @param {!Element} img image à décoder.
				 */
				ev.b64.ie7FixImg = function(img) {
					// stop the CSS expression from being endlessly evaluated
					img.runtimeStyle.behavior = 'none';
					// apply the fix
					img.src = ev.b64.ie7FixSrc(img.src);
				};

				/**
				 * Permet d'interroger l'ERA pour récupérer des
				 * images binaires pour IE7 (qui ne supporte pas
				 * les images encodées en Base64) à partir des
				 * codes en base64.
				 *
				 * @param {Array.<Element>=} imgs (optionnel) tableau
				 *   d'images à traiter (par défaut : toutes les images
				 *   de la page).
				 */
				ev.b64.ie7FixAll = function(imgs) {
					imgs = imgs || document.images;
					var img, x = 0, n = imgs.length;
					while (n) {
						--n;
						img = imgs[n];

						// pour chaque image on check le 'src'
						if (BASE64_DATA.test(img.src)) {
							// et on met l'adresse de l'image demandée sur l'ERA (qui va convertir le code Base64 en données binaires)
							img.src = BASE64_PATH + RegExp.$2 + '.' + RegExp.$1;
							++x;
						}
					}
					if (x) {
						debugMsg('b64#ie7FixAll(): ' + x + ' image(s) en Base64 décodées (patch IE7 et moins) !');
					}
				};

				debugMsg(CORE_PFX + INIT_METHOD_NAME + ': showing upgrade advice in one second...');

				/**
				 * Affichage d'un conseil de mise à jour du navigateur pour
				 * les versions d'IE trop anciennes.
				 */
				delay(function() {
					var ctn = domElement('upgAdvice'), a, img;

					if (ctn) {
						a = createElement('a');
						img = createElement('img');

						img.src = PATH.img + '/global/upg.opt.jpg';
						img.alt = '';
						img.border = '0';
						img.height = '42';
						img.width = '790';
						a.appendChild(img);

						a.target = '_blank';
						//a.href = HTTP_SCHEME + 'goo.gl/gMutf'; // IE 8-9
						a.href = HTTP_SCHEME + 'goo.gl/OZXMk'; // Chrome
						ctn.appendChild(a);

						ctn.className = 'on';
					}
				}, 2500);
			}
		}
		else {
			// Si on est pas sur IE, la méthode d'annulation de l'evenement IE ne fera rien
			window.cancelIEEvent = function() {};
		}

		/**
		 * Ajoute un comportement javascript dans un attribut d'élément
		 *  - e: l'élément
		 *  - attributeName: nom de l'attribut à modifier
		 *  - newJavascript: le code javascript à ajouter
		 *  - trail: (optionel) si true le javascript est ajouté en fin d'attribut, si false, il est ajouté au début, par
		 *    défaut true;
		 **/
		window.addJavascriptToAttribute = function(e,attributeName,newJavascript,trail) {
			e = domElement(e);
			if (!e) {return;}
			if (!attributeName) {return;}
			if (newJavascript === UNDEFINED) {return;}
			// true par défaut
			trail = (trail === UNDEFINED) || trail;
			newJavascript = newJavascript.replace(/^\s*javascript:\s*/, '').replace(/\s*;\s*$/, '');

			//var attribute=e.attributes[attributeName];
			var attribute = e.getAttributeNode(attributeName);

			if (attribute && attribute.nodeValue) {
				var oldJavascript = attribute.nodeValue;
				oldJavascript = oldJavascript.replace(/^\s*javascript:\s*/, '').replace(/\s*;\s*$/, '');
				if (trail) {
					newJavascript = oldJavascript + ';' + newJavascript;
				}
				else {
					newJavascript = newJavascript + ';' + oldJavascript;
				}
			}
			if (!attribute) {
				attribute = document.createAttribute(attributeName);
				e.setAttributeNode(attribute);
			}

			//if (e.id === 'formMEH') warnMsg(CORE_PFX + '_addJavascriptToAttribute(): 2 -> "' + e.id + '", "' + attributeName + '", "' + newJavascript + '", "' + trail + '"');
			// mise à jour réelle de l'attribut
			innerAttributeUpdate(attribute, newJavascript + ';');
			//if (e.id === 'formMEH') { warnMsg(CORE_PFX + '_addJavascriptToAttribute(): 3 -> "' + e.id + '", "' + attributeName + '", "' + newJavascript + '", "' + trail + '" [result: ' + e.getAttributeNode(attributeName).nodeValue + ']'); }
		};

		/**
		 * Modifie un comportement javascript dans un attribut d'élément, i.e. crée le code, ou remplace un éventuel Javascript précédemment mis en
		 * place, dans l'attribut désigné.
		 *  - e: l'élément
		 *  - attributeName: nom de l'attribut à modifier
		 *  - newJavascript: le code javascript à appliquer
		 **/
		window.modifyJavascriptInAttribute = function(e,attributeName,newJavascript) {
			e = domElement(e);
			if (!e) {return;}
			if (!attributeName) {return;}
			if (newJavascript === UNDEFINED) {return;}

			newJavascript = newJavascript.replace(/^\s*javascript:\s*/, '').replace(/\s*;\s*$/, '');

			// var attribute=e.attributes[attributeName];
			var attribute = e.getAttributeNode(attributeName);
			if (!attribute) {
				attribute = document.createAttribute(attributeName);
				e.setAttributeNode(attribute);
			}

			//if (e.id === 'formMEH') warnMsg(CORE_PFX + '_modifyJavascriptInAttribute(): 2 -> "' + e.id + '", "' + attributeName + '", "' + newJavascript + '"');
			// mise à jour réelle de l'attribut
			innerAttributeUpdate(attribute, newJavascript + ';');
			//if (e.id === 'formMEH') warnMsg(CORE_PFX + '_modifyJavascriptInAttribute(): 3 -> "' + e.id + '", "' + attributeName + '", "' + newJavascript + '" [result: ' + e.getAttributeNode(attributeName).nodeValue + ']');
		};


		/**
		 * Un bug présent sur IE cause un comportement erratique des tableaux chargés par JSON. En effet, la structure suivante:
		 * [
		 *  {data1},
		 *  {data2},
		 * ]
		 * ne compte en théorie que DEUX datas. Ceci est correctement géré par Firefox, mais IE en compte TROIS, à cause de la
		 * virgule supplémentaire après {data2}. Il est possible que d'autre navigateurs soient sujets au même bug. La fonction
		 * suivante effectue la correction, en supprimant dans le tableau tout élément null.
		 **/
		window.normalizeJSONArray = function(dataArray) {
			var newDataArray = [], i, l = dataArray.length;
			for (i = 0; i < l; i++) {
				if (dataArray[i]) {
					newDataArray.push(dataArray[i]);
				}
			}
			return newDataArray;
		};

		/**
		 * Cette fonction effectue le tranfert d'un jeu de paramètres d'URL vers les propriétés value
		 * d'éléments de la page. Ce transfert s'effectue par le biais de la variable langParamIdArray
		 * qui définit pour chaque paramètre dans une langue donnée l'identifiant de l'élément dont la
		 * value doit être modifié. La liste de ces paramétrage étant stockée dans un tableau d'objets
		 * disposant des propriétés lang, param, et id. lang définit la langue telle qu'elle est définie
		 * pour la page dans ev.lang. param, définit pour la langue précédente, le paramètre d'URL
		 * qui va contenir la valeur à affecter à l'élément. id contient sous forme de chaine, l'identifiant
		 * de l'élément à affecter.
		 * Exemple de la déclaration d'un langParamIdArray en format JSon
		 * [
		 *  {lang: "fr_FR", param: "depart", id: "lieuMEVDepartAller", id2: "iataMEVDepartAller"},
		 *  {lang: "es_ES", param: "origen", id: "lieuMEVDepartAller", id2: "iataMEVDepartAller"},
		 *  {lang: "it_IT", param: "partenza", id: "lieuMEVDepartAller", id2: "iataMEVDepartAller"},
		 *  {lang: "en_EN", param: "departure", id: "lieuMEVDepartAller", id2: "iataMEVDepartAller"},
		 *  {lang: "de_DE", param: "abflug", id: "lieuMEVDepartAller", id2: "iataMEVDepartAller"},
		 *  {lang: "fr_FR", param: "arrivee", id: "lieuMEVArriveeAller", id2: "iataMEVArriveeAller"},
		 *  {lang: "es_ES", param: "destino", id: "lieuMEVArriveeAller", id2: "iataMEVArriveeAller"},
		 *  {lang: "it_IT", param: "destinazione", id: "lieuMEVArriveeAller", id2: "iataMEVArriveeAller"},
		 *  {lang: "en_EN", param: "arrival", id: "lieuMEVArriveeAller", id2: "iataMEVArriveeAller"},
		 *  {lang: "de_DE", param: "ankunft", id: "lieuMEVArriveeAller", id2: "iataMEVArriveeAller"}
		 * ];
		 * @param {!Array.<Object>} langParamIdArray tableau d'objets (contenant les propriétés lang, param, id et id2).
		 * @return {!Array.<Element>} tableau des éléments remplis avec une chaine non vide (ou un tableau vide si aucun ; jamais null).
		 */
		window.transfertLangURLToElement = function(langParamIdArray) {
			if (langParamIdArray) {
				var i, value, elt1, elt2, filledElements = [];
				for (i = 0; i < langParamIdArray.length; i++) {
					if (langParamIdArray[i].lang === LANG.current) {
						value = getParameter(langParamIdArray[i].param);
						elt1 = domElement(langParamIdArray[i].id);
						elt2 = domElement(langParamIdArray[i].id2);
						if (value) {
							if (elt1) {
								elt1.value = value;
								filledElements.push(elt1);
							}
							if (elt2) {
								elt2.value = value;
								filledElements.push(elt2);
							}
						}
					}
				}
				return filledElements;
			}
			else {
				errorMsg('tools#transfertLangURLToElement(): langParamIdArray should be correctly defined as an array. [' + langParamIdArray + ']');
				return [];
			}
		};

		/**
		 * Cette fonction récupère le style passé en paramètre de l'élément passé en paramètre
		 *
		 * Returns the value of a particular style as it actually defined i.e. defined by a
		 * style attribute, or a style sheet, or the default navigator's behaviour.
		 * @param {!(string|Element)} e The element owning the style we get the value.
		 * @param {!string} styleName The style name to look for. Use the same definition as in css-style
		 * not as usually defined in JavaScript (for example: USE border-top-color,
		 * NOT borderTopColor).
		 * @return {!string} A string containing the value or "none" if the style does not exist.
		 */
		window.getStyleValue = function(e,styleName) {
			e = domElement(e);
			if (!e) {throw 'param elt ' + ERR_IS_NOT_VALID + ' DOM element';}
			if (!isString(styleName)) {throw 'styleName ' + ERR_IS_NOT_VALID + ' property name (string)';}
			//variable temporaire
			var value;
			if (window.getComputedStyle) {
				var cssStyleDeclaration = window.getComputedStyle(e, NULL);// should never be null
				value = cssStyleDeclaration.getPropertyValue(styleName);// can be empty if the style does not exist
				if (value === '') {throw 'unknown style: ' + styleName;}
				else {return value;}
			}
			else if (e.currentStyle) {// Works with MSIE 5+
				// contains the current style of the element (all the styles given by style
				// attribute, css, or default behaviour.
				var currentStyle = e.currentStyle;
				value = currentStyle.getAttribute(styleName);
				// can be null for a composed style name (ex: border-top-color) if
				// the attribute is managed by the navigator.
				// Examples: border-top-color is managed by IE6 and IE7 both navigators
				// change it to borderTopColor. max-width is managed by IE7, but is not
				// by IE6. IE7 uses maxWidth, IE6 uses max-width.
				if (!isString(value)) {
					// While there is a - in styleName, it is removed and the following
					// letter is upper cased.
					// Ex: toto-titi-tutu -> totoTiti-tutu -> totoTitiTutu
					while (styleName.indexOf('-') >= 0) {
						// dashAndLetter contains the first - and the following letter
						var dashAndLetter = styleName.charAt(styleName.indexOf('-') + 1);
						styleName = styleName.replace(/-\S{1}/, dashAndLetter.toUpperCase());
					}
					value = currentStyle.getAttribute(styleName);
				}
				if (isString(value)) {return value;}
				else {return 'none';}
			}
			else {
				throw 'cannot retrieve styleName: ' + styleName;
			}
		};

		//DEBUG
		// var maxWidth=getStyleValue(paneNode,"max-width");
		//log('max width (by easy : \''+maxWidth+'\')', 'info');
		//log('max width (by proto : \''+protoGetStyle(paneNode, "max-width")+'\')', 'info');
		//String.prototype.camelize=function() {
		// var parts = this.split('-'), len = parts.length;
		// if (len === 1) return parts[0];
		//
		// var camelized = this.charAt(0) === '-'
		// ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
		// : parts[0];
		//
		// for (var i = 1; i < len; i++)
		// camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
		//
		// return camelized;
		// };
		//String.prototype.capitalize=function(){
		// return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
		// };
		//window.protoGetStyle=function(e, style) {
		// e = domElement(e);
		// if(!e){return NULL;}
		// style = (style === 'float' || style === 'cssFloat') ? 'styleFloat' : style.camelize();
		// var value = e.style[style];
		// if (!value && e.currentStyle) value = e.currentStyle[style];
		//
		// if (style === 'opacity') {
		// if (value = (e.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
		// if (value[1]) return parseFloat(value[1]) / 100;
		// return 1.0;
		// }
		//
		// if (value === 'auto') {
		// if ((style === 'width' || style === 'height') && (e.getStyle('display') != 'none'))
		// return e['offset' + style.capitalize()] + 'px';
		// return NULL;
		// }
		// return value;
		// };

		/**
		 * Cette fonction permet de diminuer la valeur de la font-size de l'élément passé en paramètre.
		 * Cette valeur doit être exprimé en pixel.
		 *
		 * @param {!(string|Element)} e est un élément (ou un identifiant d'élément) DOM.
		 * @param {!number} minSize (optionnel) est la valeur minimale que la font-size peut prendre [par défaut : pas de valeur minimale].
		 */
		window.decreaseFontSize = function(e, minSize) {
			e = domElement(e);
			if (!e) {return;}
			var fontSize = window.getStyleValue(e, 'font-size');
			if (!fontSize.match(/[0-9]+px/)) {
				// On ne peut pas traiter des tailles qui ne sont pas exprimées en px
				return;
			}
			var fontSizeValue = parseInt(fontSize.replace(/px/, ''), 10);
			if (minSize && fontSizeValue <= minSize) {
				// Si une taille minimale est précisée et atteinte, on ne fait rien
				return;
			}
			e.style.fontSize = (fontSizeValue - 1) + 'px';
		};

		/**
		 * Cette fonction permet d'augmenter la valeur de la font-size de l'élément passé en paramètre.
		 * Cette valeur doit être exprimé en pixel.
		 *
		 * @param {!(string|Element)} e est un élément (ou un identifiant d'élément) DOM.
		 * @param {!number} maxSize (optionnel) est la valeur maximale que la font-size peut prendre [par défaut : pas de valeur maximale].
		 */
		window.increaseFontSize = function(e, maxSize) {
			e = domElement(e);
			if (!e) {return;}
			var fontSize = window.getStyleValue(e, 'font-size');
			if (!fontSize.match(/[0-9]+px/)) {
				// On ne peut pas traiter des tailles qui ne sont pas exprimées en px
				return;
			}
			var fontSizeValue = parseInt(fontSize.replace(/px/, ''), 10);
			if (maxSize && fontSizeValue >= maxSize) {
				// Si une taille maximale est précisée et atteinte, on ne fait rien
				return;
			}
			e.style.fontSize = (fontSizeValue + 1) + 'px';
		};

		/**
		 * expression rationnelle pour extraire le contenu d'une balise XML préfixée par CDATA
		 */
		window.stripCDATA = (function() {
			// Variable privée contenant l'expression régulière compilée
			var innerREGEX = /<!\[CDATA\[([^\]]*)\]\]>/;
			return function(txt) {
				var groups = innerREGEX.exec(txt);
				if (groups) {return groups[1];}
				return txt;
			};
		}());

		/**
		 *Deprecated : Alias utilise pour compatibilite
		 */
		window.getChildNodesByNodeName = domChildren;

		window.firstChildByNodeName = function(e, nodeName) {
			e = domElement(e);
			if (!e) {
				return NULL;
			}
			var i, l = e.childNodes.length;
			for (i = 0; i < l; i++) {
				if (e.childNodes[i].nodeName === nodeName) {
					return e.childNodes[i];
				}
			}
			return NULL;
		};
		window.firstChildByNodeType = function(e, nodeType) {
			e = domElement(e);
			if (!e) {
				return NULL;
			}
			var i, l = e.childNodes.length;
			for (i = 0; i < l; i++) {
				if (e.childNodes[i].nodeType === nodeType) {
					return e.childNodes[i];
				}
			}
			return NULL;
		};

		/* ########################################## */
		/* ########################################## */
		/* ### OUTILS DE MANIPULATIONS DE CLASSES ### */
		/* ########################################## */
		/* ########################################## */

		/**
		 * Méthode permettant de déclarer un héritage de classe.
		 *
		 * NB : la fonction suivante pourrait ne fonctionner que
		 * sur les navigateur récents :
		 *    function extend(_child, _super){
		 *      _child.prototype.__proto__=_super.prototype;
		 *      _child.prototype.__super=_super;
		 *    }
		 *
		 * @see #isInstanceOf(!Object, !(Object|Function))
		 *
		 * @param {!(Object|Function)} _child classe fille.
		 * @param {!(Object|Function)} _super classe mère de laquelle hérite la classe fille.
		 * @throws si _child est null ou non défini
		 * @throws si _super est null ou non défini
		 * @throws si _child n'est pas une classe (pas de prototype)
		 * @throws si _super n'est pas une classe (pas de prototype)
		 */
		function extend(_child, _super) {
			if (!_child) {throw 'Classe.extend: _child class ' + ERR_IS_NOT_VALID;}
			if (!_super) {throw 'Classe.extend: _super class ' + ERR_IS_NOT_VALID;}
			if (!_child.prototype) {throw 'Classe.extend: Not a class : ' + _child;}
			if (!_super.prototype) {throw 'Classe.extend: Not a class : ' + _super;}
			var p,
					// Hack for reserved name '__proto__'
					pr = '__proto__';

			// copie de toutes les propriétés de _super vers _child
			// (pour compatibilite anciens navigateurs ne supportant pas l'utilisation de prototype.__proto__)
			for (p in _super.prototype) {
				if (typeof(_child.prototype[p]) === 'undefined') {
					_child.prototype[p] = _super.prototype[p];
				}
			}
			// transfert du prototype de _super sur la propriété '__proto__' du prototype de _child (nouveaux navigateur)
			_child.prototype[pr] = _super.prototype;
			// stokage de la référence sur la super classe (pour meilleure performance de la méthode #isInstanceOf
			_child.prototype.__super = _super;
			return _child;
		}

		/**
		 * Fonction permettant de savoir si un objet (ou une classe) est
		 * une instance d'une classe donnée ou d'une classe dérivée.
		 *
		 * NOTA : une classe peut être considérée comme une instance d'une
		 * de ses super classes.
		 *
		 * La classe de l'objet _child doit être _super ou avoir été étendue de
		 * la classe _super ou d'une de ses classes filles (sinon le résultat
		 * sera 'faux').
		 * Toutes les classes de la chaine doivent avoir été étendues via la
		 * méthode extend(). Dans le cas contraire, l'héritage n'est pas
		 * supporté par cette méthode isInstanceOf(). La méthode renverra alors
		 * 'vrai' seulement si _child est une instance directe de la classe
		 * _super.
		 *
		 * @see #extend(!(Object|Function), !(Object|Function))
		 *
		 * @param {!Object} _child instance d'un objet de n'importe quelle classe fille de _super (ou une classe fille de _super, ou _super elle-même).
		 * @param {!(Object|Function)} _super classe mère de laquelle doit hériter la classe fille.
		 * @throws si _child est null ou non défini
		 * @throws si _super est null ou non défini
		 * @throws si _super n'est pas une classe (pas de prototype)
		 * @return {boolean} true si la classe de _child est étendue (directement ou pas) de la classe _super.
		 */
		function isInstanceOf(_child, _super) {
			// FIXME rendre ITERATIVE (c'est quand même plus performant)
			if (!_child) {throw 'Classe.isInstanceOf: _child class ' + ERR_IS_NOT_VALID;}
			if (!_super) {throw 'Classe.isInstanceOf: _super class ' + ERR_IS_NOT_VALID;}
			if (!_super.prototype) {throw 'Classe.isInstanceOf: Not a class : ' + _super;}

			// si _child n'est pas une classe, c'est une instance d'objet
			if (!_child.prototype) {
				// si la classe de _child est _super, on renvoi vrai (si le navigateur supporte l'héritage de classe, l'opérateur 'instanceof' suffira lorsque la réponse est 'vrai')
				if (_child instanceof _super) {return TRUE;}
				// sinon, si la classe de _child a une super classe, on teste si cette super classe est _super (ou étendue de _super)
				// Pour cela, il suffit de tester de façon récursive, si cette super classe est une instance de _super
				if (_child.__super) {return _child.__super === _super || isInstanceOf(_child.__super, _super);}
				// sinon, on renvoie 'faux' (_child n'est pas une instance de _super)
				return FALSE;
			}

			// si _child est une classe, on teste si cette classe est _super (ou étendue de _super)
			if (_child === _super) {return TRUE;}
			// ... ou, si elle a une super classe étendue de _super
			if (_child.prototype.__super) {return _child.prototype.__super === _super || isInstanceOf(_child.prototype.__super, _super);}
			// sinon, on retourne 'faux' (_child n'hérite pas de _super)
			return FALSE;
		}

		/**
		 * Permet de verifier qu'une classe est définie.
		 *
		 * @param {Object|Function} jsClass nom de classe (ou de namespace) à vérifier.
		 */
		function checkDefined(jsClass) {
			//    debugMsg("Classe.checkDefined: test d'existance de la classe '"+jsClass+"'");
			if (isFunction(window[jsClass])) {return;}
			// si nom complet (avec namespace)
			var names = jsClass.split(/\./);
			var nameCnt = names.length;
			var lastNamespace = window;
			var i = 0;
			while (lastNamespace) {
				lastNamespace = lastNamespace[names[i]];
				++i;
				if (i >= nameCnt) {
					if (isFunction(lastNamespace)) {return;}
					break;
				}
			}
			//throw 'Classe.checkDefined: class ' + jsClass + ' ' + ERR_IS_NOT_VALID + '!';
			errorMsg('Classe.checkDefined: class ' + jsClass + ' ' + ERR_IS_NOT_VALID + '!', TRUE);
		}

		// Mise à disposition dans le namespace (package) des outils pour les classes
		ev.Classe = (window.Classe = {
			extend: extend,
			isInstanceOf: isInstanceOf,
			checkDefined: checkDefined
		});

		/**
		 * Fonction permettant de gerer des onglet de façon générique
		 *
		 * @param {!string} _idBaseBlocOnglet chaine de caractere correspondant a la base commune des ids des bloc correspondant aux onglets.
		 * @param {!string} _idBaseOnglet chaine de caractere correspondant a la base commune des ids des onglets (permet de mettre la class selected sur l'onglet selectionné).
		 * @param {!number} _numOngletCourant numero de l'onglet à afficher.
		 * @param {!number} _nbOnglets nombre total d'onglets.
		 */
		window.displayOnglet = function(_idBaseBlocOnglet, _idBaseOnglet, _numOngletCourant, _nbOnglets) {
			if (!_idBaseBlocOnglet) {throw '_idBaseBlocOnglet ';}
			if (!_idBaseOnglet) {throw '_idBaseOnglet ' + ERR_IS_NOT_VALID;}
			if (!isNumber(_numOngletCourant)) {throw '_numOngletCourant ' + ERR_IS_NOT_VALID + ' number';}
			if (!isNumber(_nbOnglets)) {throw '_nbOnglets ' + ERR_IS_NOT_VALID + ' number';}

			var i, blocOnglet, onglet;
			for (i = 1; i <= _nbOnglets; ++i) {
				blocOnglet = domElement(_idBaseBlocOnglet + i);
				if (!blocOnglet) {throw "l'element html dont l'id est : " + _idBaseBlocOnglet + i + " correspondant au bloc de l'onglet courant n'a pas pu etre trouvé";}

				onglet = domElement(_idBaseOnglet + i);
				if (!onglet) {throw "l'element html dont l'id est : " + _idBaseOnglet + i + " correspondant a l'onglet courant n'a pas pu etre trouvé";}

				if (i === _numOngletCourant) {//on affiche cet element
					blocOnglet.style.display = 'block';
					addClass(onglet, 'selected');
					removeClass(onglet, 'unselected');
				}
				else {//on cache l'element
					blocOnglet.style.display = 'none';
					addClass(onglet, 'unselected');
					removeClass(onglet, 'selected');
				}
			}
		};

		//on initialise le tableau qui contiendra les objets à "post-loader"
		var arrayPostLoad = [];
		window.postLoad = function() {
			//cette méthode permet de construire le tableau d'objets contenants les urls des images et leurs éléments correspondants
			this.addIMG = function(eltIMG,urlIMG) {
				if (!eltIMG) {throw 'eltIMG is undefined';}
				arrayPostLoad.push({elt: eltIMG, url: urlIMG});
			};
			//cette méthode effectue le remplacement de l'image initiale
			this.load = function() {
				var i;
				for (i = 0; i < arrayPostLoad.length; i++) {
					arrayPostLoad[i].elt.src = arrayPostLoad[i].url;
				}
			};
		};

		ev.fx = {
			/**
				 * Cette fonction permet d'animer un texte avec 3 petits
				 * point à la fin de manière répétitive.<br>
				 * <br>
				 * ex : "En cours", puis "En cours.", puis "En cours..",
				 * puis "En cours...", puis on retourne à la valeur initiale.
				 *
				 * @param {(string|Element)} element element DOM (ou son id) dont le texte doit varier.
				 * @param {!string} property le nom de la propriété à changer sur l'élément donné ('innerHTML' par ex ; 'value' pour un 'input').
				 * @param {string=} textPrefix le texte à utiliser (avant les 3 petits points ; optionnel).
				 */
			dot3: function(element, property, textPrefix) {
				//infoMsg('ev.fx.dot3> '+element);
				// On accepte soit un nom d'élément soit un objet élément
				element = domElement(element);
				//infoMsg('ev.fx.dot3> '+element);
				// on définit l'enchainement de tâches que si l'élément est correct
				if (!element) { return; }
				// si la méthode #stopDot3() existe sur l'élément c'est que le process est déjà lancé
				if (isFunction(element.stopDot3)) {
					// ...donc on appelle la méthode, le process va se stopper et le nettoyage sera fait
					element.stopDot3();
				}
			// sinon, si l'objet de travail de dot3 est là c'est que dot3 est en cours de mise en place
				else if (element.$ev$fx$dot3) {
					// ...donc on sort
					return;
				}
				// Création d'une propriété sur l'élément pour la gestion du processus et son arrêt
				var dot3Object = (element.$ev$fx$dot3 = {}),
						// Variable qui va prendre les valeurs de '' '.' '..' et '...' pendant le processus
						textSuffix = '';
				// si la propriété n'est pas définie on choisi 'innerHTML'
				property = property || 'innerHTML';
				// si le texte n'est pas défini, on choisi celui qu'il y a au départ
				if (textPrefix === UNDEFINED) {
					textPrefix = element[property];
				}
				/**
					 * Fonction de modification de l'élément donné.
					 *
					 * @param {string} txt text à afficher sur l'élement.
					 */
				function set(txt) {
					//debugMsg('ev.fx.dot3> set> -> "'+txt+'"');
					element[property] = txt;
				}

				/**
					 * Fonction qui gère la succession des tâches de modification
					 * du texte de l'élément donné.
					 */
				function dot() {
					//warnMsg('ev.fx.dot3> dot> setting ('+textPrefix+textSuffix+')...');
					// si la propriété 'stop' est définie non-nulle c'est que la fonction stop() a été appelée
					if (!dot3Object.stop) {
						//warnMsg('ev.fx.dot3> dot> not stopped => continue...');
						// sinon, on affiche la séquense de texte courante...
						set(textPrefix + textSuffix);
						//warnMsg('ev.fx.dot3> dot> text printed ('+textPrefix+textSuffix+') => preparing next...');
						// s'il y a déjà 3 points, on enlève tout, sinon on rajoute un point
						textSuffix = textSuffix.length > 2 ? '' : textSuffix + '.';
						//warnMsg('ev.fx.dot3> dot> next text prepared ('+textPrefix+textSuffix+') => scheduling...');
						dot3Object.to = delay(dot, 250);
						//warnMsg('ev.fx.dot3> dot> next text scheduled ('+textPrefix+textSuffix+')');
					}
				}
				/**
					 * Fonction d'arrêt de la séquense de 3 points.
					 * Elle interrompt l'enchainement précédemment démarré.<br>
					 * Elle affiche sur l'élément correspondant le texte donné
					 * (ou le texte donné au départ si aucun paramètre ici)
					 *
					 * @param {string} text texte à afficher à l'arrêt.
					 */
				function stop(text) {
					//warnMsg('ev.fx.dot3> stop> with text "'+text+'"');
					if (text === UNDEFINED) {
						text = textPrefix;
					}
					// on fixe la prop 'stop' à 1 pour être sûr qu'aucune tâche ne sera relancée entre temps
					dot3Object.stop = 1;
					//warnMsg('ev.fx.dot3> stop> stop -> 1');
					// et on coupe la prochaine (s'il y en a une programmé)
					window.clearTimeout(dot3Object.to);
					//warnMsg('ev.fx.dot3> stop> timeout cleared');
					// on place le texte donné (ou le préfixe de départ si aucun texte donné)
					set(text);
					//warnMsg('ev.fx.dot3> stop> text set ('+text+')');
					// nettoyage
					element.$ev$fx$dot3 = NULL;
					element.stopDot3 = NULL;
				}

				// stockage de la fonction d'arrêt dans l'élément DOM donné
				element.stopDot3 = stop;
				//infoMsg('ev.fx.dot3> starting dots...');
				// premier appel
				dot();
				//infoMsg('ev.fx.dot3> started.');
			}
		};

		/**
		 * Fonction de validation automatique des champs lieux donnés.<br>
		 * <b>NB :</b> la validation est <b>asynchrone</b>!
		 *
		 * @param {?(Element|string)} eltText élément texte à utiliser pour valider le lieu.
		 * @param {?(Element|string)} eltData élément data caché qui doit être rempli (en même temps que le champ texte après la validation).
		 * @param {!string} moteur code segment du moteur (optionnel ; par défaut : 'MEV').
		 * @return {boolean} <code>true</code> si le PropositionManager est dispo, sinon <code>false</code>.
		 */
		function autoValidLieu(eltText, eltData, moteur) {
			eltText = domElement(eltText);
			eltData = domElement(eltData);
			if (ev.me && ev.me.PropositionManager) {
				if (eltText && eltData) {
					// Si tous les éléments sont là... On utilise le gestionnaire de propositions
					// On s'assure que le champ data est vide avant de commencer
					eltData.value = '';
					// Pour le remplir ensuite, on va valider le champ texte correspondant (avec le PropositionManager)
					(new ev.me.PropositionManager.ValidationRequest({
						str: eltText.id || eltText,
						code: eltData.id || eltData,
						moteur: moteur
					})).callSend();
					//debugMsg(CORE_PFX + '_autoValidLieu(): lancée pour ' + eltText.id + ' = ' + eltText.value + ' et ' + eltData.id + '!');
				}
				return TRUE;
			}
			return FALSE;
		}

		/**
		 * Effectue une redirection vers la page demandée ou vers la page
		 * spécifiée dans la propriété 'value' du champ de formulaire donné.
		 *
		 * NOTA: si le paramètre est nul ou vide, il ne se passe rien.
		 *
		 * @param {?(Element|string)} redir Champ de formulaire 'input' ou chaine de caractères.
		 */
		function redirect(redir) {
			if (redir) {
				if (isString(redir)) {
					location.href = redir;
				}
				else if (redir.value) {
					location.href = redir.value;
				}
			}
		}

		/**
		 * Ajoute une méthode de redirection sur le champ
		 * de formulaire donné.
		 *
		 * Cette méthode renverra le navigateur vers l'url
		 * qui sera récupérée dans la propriété 'value' du
		 * champ donné.
		 *
		 * NOTA: Si le champ donné (ou la méthode) est nul(le)
		 * il ne se passe rien.
		 *
		 * @param {?Element} field champ de formulaire.
		 */
		function addRedirectHandler(field, method) {
			if (field && method) {
				field[method] = function() {
					redirect(field);
				};
			}
		}

		ev.forms = {
			/**
				 * Rempli automatiquement les champs donnés par les valeurs
				 * trouvées dans le tableau de mappings.<br>
				 * Les valeurs choisies dans le tableau sont celles correspondant
				 * à la langue courante.<br>
				 * <b>NOTA :</b> sur l'élément texte, un attribut '$$evAutoFilled' est
				 * fixé à <code>true</code> pour indiquer aux gestionnaires de
				 * complétion que le champ texte a été rempli automatiquement.
				 * Ceci évite un affichage des propositions d'aide à la saisie
				 * lorsqu'il s'agit d'un pré-remplissage.
				 *
				 * @param {!(string|Element)} eltLieu element (ou son id) texte du lieu à remplir.
				 * @param {!(string|Element)} eltLieuHidden element (ou son id) hidden du lieu à remplir.
				 * @param {!Array.<Object>} mappings tableau des mappings à utiliser (par langues).
				 * @param {string=} autoValidMoteur nom du moteur pour valider les champs remplis automatiquement (optionnel ; par défaut, pas de validation auto).
				 */
			autoFillLieuFromUrl: function(eltLieu, eltLieuHidden, mappings, autoValidMoteur) {
				eltLieu = domElement(eltLieu);
				eltLieuHidden = domElement(eltLieuHidden);
				// Calcul du nombre de mappings à traiter
				var n = (mappings && mappings.length) || 0,
						filledElements;

				//debugMsg(['autoFillLieuFromUrl(): entree -',
				//    'eltLieu='+(eltLieu? eltLieu.id+','+eltLieu.value: 'null'),
				//    '; eltHidden='+(eltLieuHidden? eltLieuHidden.id+','+eltLieuHidden.value: 'null'),
				//    '; mappings='+n]);

				// affectation des identifiants de champs à remplir à tous les mappings
				if (!autoValidMoteur) {
					// on ne rempli le 2e champ que si la validation automatique n'est pas demandée
					// si elle est demandée, cela ne sert à rien car il serait écrasé
					while (n) {
						--n;
						mappings[n].id = eltLieu;
						mappings[n].id2 = eltLieuHidden;
					}
				} else {
					while (n) {
						--n;
						mappings[n].id = eltLieu;
					}
				}

				// exécution de tous les remplissages
				filledElements = window.transfertLangURLToElement(mappings);

				// Sur les champs remplis, on va fixer la propriété '$$evAutoFilled' à TRUE
				n = filledElements.length;
				while (n > 0) {
					--n;
					// Seulement si le champ comporte au moins 3 caractères
					if (filledElements[n].value.length > 2) {
						filledElements[n].$$evAutoFilled = TRUE;
					}
				}

				// Validation des champs (si c'est demandé en paramètre)
				if (autoValidMoteur) {
					if (!autoValidLieu(eltLieu, eltLieuHidden, autoValidMoteur) && eltLieuHidden) {
						// Si le système de validation n'est pas disponible (ancien moteur),
						// on met le contenu du champ texte dans le champ data
						eltLieuHidden.value = eltLieu.value;
					}
				}

				//debugMsg(['autoFillLieuFromUrl(): sortie -',
				//    'eltLieu='+(eltLieu? eltLieu.id+','+eltLieu.value: 'null'),
				//    '; eltHidden='+(eltLieuHidden? eltLieuHidden.id+','+eltLieuHidden.value: 'null')]);
			},

			util: {
				/**
					 * Fixe une classe 'onError' au champ de formulaire donné.
					 * @param {!Element} f élément correspondant à un champ de formulaire.
					 * @param {string=} errCls nom de la classe d'erreur à utiliser (optionnel ; par défaut 'onError').
					 */
				setFieldError: function(f, errCls) {
					addClass(f, errCls || 'onError');
				},

				/**
					 * Supprime la classe 'onError' du champ de formulaire donné.
					 * @param {!Element} f élément correspondant à un champ de formulaire.
					 * @param {string=} errCls nom de la classe d'erreur à utiliser (optionnel ; par défaut 'onError').
					 */
				resetFieldError: function(f, errCls) {
					removeClass(f, errCls || 'onError');
				},

				/**
					 * capTextField : Supprime les espaces multiples du texte
					 * et coupe le restant après 'limit' caractères.<br>
					 * NOTA: si on ne précise pas la limite ou si on choisi 0,
					 * la valeur utilisée sera 30.
					 *
					 * @param {!Element} f Champ de texte formulaire.
					 * @param {number=} limit Nombre de caractères acceptés [optionnel ; par défaut 30].
					 */
				capTextField: function(f, limit) {
					limit = limit || 30;
					var val = f && f.value;
					if (val) {
						val = val.replace(REGEXP_SPACE, ' ');
						if (val.length > limit) {
							val = val.substring(0, limit);
						}
						f.value = val;
					}
				},

				redirect: redirect,
				addRedirectHandler: addRedirectHandler
			}
		};

		debugMsg('tools#' + INIT_METHOD_NAME + ': ok');
	}()); // Fin de ev.tools - Exécution de tools
}()); // Fin de ev.core - Exécution globale

