

/**
* Librairie remote Javascript (rjs)
*
* Fonctionnement:
* Un fichier javascript distant contient par dï¿½finition un ensemble de propriï¿½tï¿½s et de mï¿½thodes. Comme le fichier
* javascript est succeptible d'ï¿½tre modifiï¿½ dynamiquement l'url concernant ce fichier peut ï¿½tre modifiï¿½e sur ï¿½vï¿½nement
* lors de la consultation de la page par l'internaute.
* L'invocation d'une mï¿½thode nï¿½cessite donc ï¿½ la fois l'url du fichier javascript ansi que la commande. Ceci est rï¿½alisï¿½
* en invoquant la mï¿½thode invoke(src,command) comme suit:
* rs=new RemoteScript();
* rs.invoke("http://scripts.somewhere.js/scripts/theScript.js?p=XXX","theFunction()");
* Dans le morceau de code prï¿½cï¿½dent la variable rs contient une rï¿½fï¿½rence ï¿½ un fichier js particulier contenant la
* fonction theFunction().
* L'instance rs qui a ï¿½tï¿½ dï¿½clarï¿½e prï¿½cï¿½demment doit ï¿½tre vu comme une source fonctionnelle de javascript. Le changement
* de l'URL doit contribuer avant tout ï¿½ modifier le comportement javascript sans changer la signature.
* Par exemple, si on doit invoquer theFunction() sur deux fichiers js diffï¿½rents:
*
* IL NE FAUT PAS FAIRE:
* rs1=new RemoteScript();
* rs1.invoke("http://scripts.somewhere.js/scripts/theScript.js?p=XXX","theFunction()");
*  ...
* rs2=new RemoteScript();
* rs2.invoke("http://scripts.somewhere.js/scripts/theScript.js?p=YYY","theFunction()");
* Car dans ce cas, deux fonctions theFunction() ont ï¿½tï¿½ crï¿½ï¿½es, il va donc y avoir ambiguitï¿½ entre ces deux fonctions
* crï¿½ï¿½es par les deux instances.
*
* IL FAUT FAIRE:
* rs=new RemoteScript();
* rs.invoke("http://scripts.somewhere.js/scripts/theScript.js?p=XXX","theFunction()");
*  ...
* rs.invoke("http://scripts.somewhere.js/scripts/theScript.js?p=YYY","theFunction()");
* La deuxiï¿½me invocation fait disparaitre la prï¿½cï¿½dente. Chaque objet RemoteScript doit incarner une fonctionnalitï¿½
* particuliï¿½re.
*
* IMPORTANT: La page HTML utilisant rjs doit contenir une section HEAD.
*
*
* TODO: Il faut paramï¿½trer davantage la partie message d'erreur
* TODO: Il faut envisager ï¿½ventuellement la possibilitï¿½ d'un valeur de retour sur invocation
**/
(function() {
	var WIN = this,
			DOC = WIN.document,
			// raccourcis vers la méthode interdite 'eval()'
			EVAL = eval,
			showErrors = false,
			DEFAULT_TIMEOUT = 1000,
			DEFAULT_DELAY = 100,
			// Pas de description, cf. TODO list.
			DISPLAY_TIME = 3000;

	function hideRSErrorMessage() {
		var divErrorMessage = DOC.getElementById('rsErrorMessage');
		if (divErrorMessage) {
			divErrorMessage.style.display = 'none';
		}
	}

	function displayRSErrorMessage(message) {
		if (showErrors) {
			var divErrorMessage = DOC.getElementById('rsErrorMessage');
			if (!divErrorMessage) {
				divErrorMessage = DOC.createElement('DIV');
				divErrorMessage.id = 'rsErrorMessage';
				var body = DOC.getElementsByTagName('BODY').item(0);
				body.appendChild(divErrorMessage);
				divErrorMessage.style.position = 'absolute';
				divErrorMessage.style.top = '0px';
				divErrorMessage.style.left = '0px';
				divErrorMessage.style.backgroundColor = 'red';
			}
			divErrorMessage.appendChild(DOC.createTextNode(message));
			divErrorMessage.appendChild(DOC.createElement('BR'));
			divErrorMessage.style.display = 'block';
			WIN.setTimeout(hideRSErrorMessage, DISPLAY_TIME);
		}
	}

	/**
	 * Pool de RemoteScript qui maintient dans la variable remoteScriptPool
	 * la totalité des instances de RemoteScript pouvant être présentes sur
	 * une même page.
	 */
	var pool = [],
			remoteScriptPool = {
				add: function(script) {
					pool.push(script);
					return pool.length - 1;
				},

				get: function(idx) {
					return pool[idx];
				}
			};

	/**
	 * Cet objet maintient le code source liï¿½ au fichier dï¿½signï¿½ par le
	 * paramï¿½tre src utilisï¿½ dans la mï¿½thode invoke(), il ï¿½vite de recharger
	 * a diverses reprises un script dï¿½jï¿½ chargï¿½. Il gï¿½re aussi le dï¿½lai
	 * de chargement du script distant, et les cas d'erreur de chargement.
	 *  - defaultSrc: contient le fichier de script par dï¿½faut utilisï¿½ lorsque
	 * le src utilisï¿½ dans invoke est inexistant. - ptimeout: (optionnel)
	 * dï¿½finit le timeout sur l'invocation des mï¿½thodes rjs. - pdelay:
	 * (optionnel) dï¿½finit l'intervalle d'invocation des mï¿½thodes rjs,
	 * limitï¿½es par le timeout.
	 */
	WIN.RemoteScript = function(defaultSrc,timeout,delay) {
		// On dï¿½finit les propriï¿½tï¿½s defaultSrc timeout et delay
		this.defaultSrc = defaultSrc;
		this.timeout = DEFAULT_TIMEOUT;
		if (timeout) {
			this.timeout = timeout;
		}
		this.delay = DEFAULT_DELAY;
		if (delay) {
			this.delay = delay;
		}

		// On ajoute l'instance courante dans le remoteScriptPool
		this.poolIndex = remoteScriptPool.add(this);

		// La propriï¿½tï¿½ scriptNode va contenir le node script qui sera ajoutï¿½ ï¿½ la page. Pour l'instant il est null, indiquant qu'aucun script n'est chargï¿½.
		this.scriptNode = null;

		// Le timestamp correspondant au dï¿½marrage de l'invocation, il sert ï¿½ interrompre l'execution au delï¿½ du tiemout
		this.commandTime = new Date();

		// Contiendra le dernier message d'erreur gï¿½nï¿½rï¿½ par eval() de la commande invoquï¿½e dans invoke(), ce message sera affichï¿½ par
		// displayRSErrorMessage() en cas de timeout
		this.cause = 'No Problem';

		/**
		 * Mï¿½thode d'invocation d'une fonction rjs.
		 * - src: attribut src du node script indiquant oï¿½ se trouve le script ï¿½ charger.
		 * - command: commande ï¿½ invoquer (soumise au timeout)
		 * - cbAfter: methode 'callback' ï¿½ executer aprï¿½s la commande
		 * ATTENTION: la commande est contenue dans une chaine de caractï¿½re, elle doit ï¿½tre gï¿½rï¿½e comme c'est fait dans les foonctions classiques de
		 * Javascript setTimeout() ou eval(), ces deux fonctions sont d'ailleurs chargï¿½es de gï¿½rer la commande.
		 **/
		this.invoke = function(src,command,cbAfter) {
			// head reference le noeud HEAD de la page
			var head = DOC.getElementsByTagName('head').item(0);
			// Si un script est dï¿½jï¿½ chargï¿½, alors il n'est peut ï¿½tre pas nï¿½cessaire de le recharger
			if (this.scriptNode) {
				// Si le script dï¿½jï¿½ prï¿½sent sur la page a le mï¿½me src que celui demandï¿½ en paramï¿½tre de la mï¿½thode, il est inutile de le recharger
				// Si en revanche il est diffï¿½rent, on doit effectuer ce chargement
				if (this.scriptNode.src !== src) {
					// Comme un script ï¿½tait dï¿½jï¿½ prï¿½sent, il faut le supprimer de la page.
					// Ce n'est pas suffisant, car si le paramï¿½tre scr ne correspond ï¿½ aucun fichier disponible, le fait d'avoir supprimï¿½ le node script
					// ne fait pas disparaitre sa trace en "mï¿½moire". Il faut donc charger le script par dï¿½faut, pour s'assurer qu'on a bien remplacï¿½ la
					// trace du script prï¿½cï¿½dent en "mï¿½moire"
					head.removeChild(this.scriptNode);
					this.scriptNode = DOC.createElement('SCRIPT');
					// Cela n'est pas obligatoire (c'est le type par défaut)
					//this.scriptNode.type="text/javascript";
					this.scriptNode.charset = 'iso-8859-1';
					this.scriptNode.src = this.defaultSrc;
					head.appendChild(this.scriptNode);

					// Puis on enlï¿½ve le script par dï¿½faut et on ajoute le script correspondant au src passï¿½ en paramï¿½tre
					head.removeChild(this.scriptNode);
					this.scriptNode = DOC.createElement('SCRIPT');
					// Cela n'est pas obligatoire (c'est le type par défaut)
					//this.scriptNode.type="text/javascript";
					this.scriptNode.charset = 'iso-8859-1';
					this.scriptNode.src = src;
					head.appendChild(this.scriptNode);
				}
			}
			// Si aucun script n'est dï¿½jï¿½ chargï¿½ on charge simplement
			else {
				this.scriptNode = DOC.createElement('SCRIPT');
				this.scriptNode.charset = 'iso-8859-1';
				this.scriptNode.src = src;
				this.scriptNode.type = 'text/javascript';
				head.appendChild(this.scriptNode);
			}
			// On invoque alors la fonction par le biais de la mï¿½thode tryCommand(), on applique d'emblï¿½e un intervalle delay pour laisser au navigateur
			// le temps de charger le script
			this.commandTime = new Date();
			// closure use (definition d'une variable visible seulement ici et par la fonction definie juste en dessous)
			var scopedPoolIndex = this.poolIndex;
			WIN.setTimeout(function() {
				remoteScriptPool.get(scopedPoolIndex).tryCommand(command, cbAfter);
			}, this.delay);
		};

		/**
		 * Cette mï¿½thode tente l'excution de la commande passï¿½e en paramï¿½tre, si cette commande n'est pas executï¿½e correctement, elle est invoquï¿½e
		 * rï¿½cursivement jusqu'ï¿½ rï¿½ussite de l'execution ou expiration du timeout.
		 * - command: contient la commande (appel de fonction) ï¿½ effectuer aprï¿½s chargement du rjs
		 * - cbAfter: methode 'callback' ï¿½ executer aprï¿½s la commande
		 **/
		this.tryCommand = function(command, cbAfter) {
			// Le bon dï¿½roulement de l'execution de la commande est controlï¿½ par un try/catch
			try {
				// Si on n'a pas encore dï¿½passï¿½ le timeout, on peut tenter l'excution de la commande
				if ((new Date()).getTime() < this.commandTime.getTime() + this.timeout) {
					EVAL(command);
					// Si on arrive ici c'est que la commande a ï¿½tï¿½ executï¿½e, et le traitement se termine
				}
				// Sinon on affiche un message d'erreur
				else {
					displayRSErrorMessage('command timeout: '+ command + ', cause: '+ this.cause.lineNumber + ' - '+ this.cause);
				}
				if (cbAfter) {
					cbAfter();
				}
			}
			// Si la commande ne s'est pas executï¿½ correctement, on invoque ï¿½ nouveau la prï¿½sente mï¿½thode aprï¿½s l'ï¿½coulement de delay
			catch (e) {
				this.cause = e;
				// closure use (definition d'une variable visible seulement ici et par la fonction definie juste en dessous)
				var scopedPoolIndex = this.poolIndex;
				WIN.setTimeout(function() {
					remoteScriptPool.get(scopedPoolIndex).tryCommand(command, cbAfter);
				}, this.delay);
			}
		};
	};
}());

