//#12.00Aa WDTable.JS
//VersionVI: 30F120055n
// Le seul support technique disponible pour cette librairie est
// accessible a travers le service "Assistance Directe".

// Attention a ne pas mettre d'accent dans ce fichier COMMENTAIRES inclus

// Classe representant une requete pour du cache
function WDCacheRequete (oChampTable)
{
	// Table liee a la requete
	this.m_oObjetTable = oChampTable;

	// Debut de la requete
	this.m_oDate = new Date();
};

WDCacheRequete.prototype =
{
	// Valeur par defaut des variables : mis dans le prototype
	m_nColonneTri:		-1,			// Tri demande
	m_nLigne:			-1,			// Ligne modifie
	m_nIdRequeteAJAX:	undefined,	// Objet de requete AJAX
	sCommandeAjax_LignesTable:		"LIGNESTABLE",
	sCommandeAjax_LignesTriTable:	"LIGNESTRITABLE",
	sCommandeAjax_ParCourtAP:		"PARCOURTAP",
	sCommandeAjax_RechercheTable:	"RECHERCHETABLE",
	sCommandeAjax_ModifTable:		"MODIFTABLE",
	sParametreAjax_ColonneLien:		"WD_TABLE_COL_LIEN_",
	sParametreAjax_Debut:			"WD_TABLE_DEBUT_",
	sParametreAjax_Nombre:			"WD_TABLE_NOMBRE_",
	sParametreAjax_CleEnreg:		"WD_TABLE_CLEENREG_",
	sParametreAjax_CleEnreg_Pos:	"WD_TABLE_CLEENREG_POS_",
	sParametreAjax_CleEnregAP:		"WD_TABLE_CLEENREGAP_",
	sParametreAjax_CleEnregAP_Pos:	"WD_TABLE_CLEENREGAP_POS_",
	sParametreAjax_TriAWP:			"WD_TABLE_TRIAWP_",
	sParametreAjax_ColTri:			"WD_TABLE_COL_TRI_",
	sParametreAjax_Recherche_Marge:	"WD_TABLE_RECHERCHE_MARGE_",
	sParametreAjax_Recherche_Nombre:"WD_TABLE_RECHERCHE_NOMBRE_",
	sParametreAjax_Recherche_Col:	"WD_TABLE_RECHERCHE_COL_",
	sParametreAjax_Recherche_Valeur:"WD_TABLE_RECHERCHE_VAL_",

	// Contruit le bout de requete supplementaire transmit en AWP
	sConstuitAWP:function (bSelection)
	{
		// Contruit le bout de requete pour la continuation du parcourt en arriere plan dans les pages AWP
		var sRequete = this.sConstuitPosAP();

		// Ajoute le tri courant de la table
		if (clWDAJAXMain.m_bPageAWP)
		{
			if (this.m_oObjetTable.m_nColonneTriePre > -1)
				sRequete += "&" + this.sParametreAjax_TriAWP + "=" + clWDEncode.sEncodePOST((this.m_oObjetTable.m_bSensTriPre ? "+" : "-") + this.m_oObjetTable.m_nColonneTriePre);

			// Ajoute la selection de la table : on utilise le champ special ajoute a la page pour transmettre la selection complete au serveur
			// sans perturber le reste
			if (!bSelection)
			{
				var sSelection = clWDAJAXMain.sConstruitValeurChamp(this.m_oObjetTable.m_oFormulaireSelAWP);
				if (sSelection.length)
					sRequete += "&" + sSelection;
			}
		}

		return sRequete;
	},

	// Contruit le bout de requete pour la continuation du parcourt en arriere plan dans les pages AWP
	sConstuitPosAP:function ()
	{
		// Si pas de position : fin
		if (this.m_oObjetTable.m_sCleParcourtAP.length == 0)
			return "";

		// Meme si on a fini le parcourt car il faut se replacer sur le serveur

		// Sinon ajoute les informations qui vont bien
		return "&" + this.sParametreAjax_CleEnregAP_Pos + "=" + this.m_oObjetTable.m_nNbLignes + "&" + this.sParametreAjax_CleEnregAP + "=" + clWDEncode.sEncodePOST(this.m_oObjetTable.m_sCleParcourtAP);
	},

	// Construit la requete pour la recherche dans une table
	sConstuitRequeteRechercheTable:function (nNombre, nMarge, sRecherche)
	{
		//	- Commande AJAX (non specifique)
		var sRequete = this.sCommandeAjax_RechercheTable + "=" + clWDEncode.sEncodePOST(this.m_oObjetTable.m_sAliasTable);
		// Nouvelle serie d'ordres
//		//	- Lignes (specifique)
//		sRequete += "&" + nMarge + "=" + nNombre;
//		//	- Recherche (specifique)
//		sRequete += "&" + this.m_nColonneTri + "=" + sRecherche;
		sRequete += "&" + this.sParametreAjax_Recherche_Marge + "=" + nMarge;
		sRequete += "&" + this.sParametreAjax_Recherche_Nombre + "=" + nNombre;
		sRequete += "&" + this.sParametreAjax_Recherche_Col + "=" + this.m_nColonneTri;
		sRequete += "&" + this.sParametreAjax_Recherche_Valeur + "=" + clWDEncode.sEncodePOST(sRecherche);

		// Ajoute la position du parcours en AP si besoin
		sRequete += this.sConstuitAWP(false);

		// On renvoie la requete creee
		return sRequete;
	},

	// Construit la requete pour recuperer les lignes d'une table
	sConstuitRequeteLignesTable:function (bTriCroissant, nCleEnregPos, sCleEnreg)
	{
		var sRequete = "";
		//	- Commande AJAX (non specifique)
		// Si on en demande pas juste le nombre de lignes
		if ((this.m_oSegment.m_nDebut != -1) && (this.m_oSegment.m_nTaille != -1))
		{
			// Selon si on demande un tri de colonnes
			sRequete = ((this.m_nColonneTri > -1) ? this.sCommandeAjax_LignesTriTable : this.sCommandeAjax_LignesTable) + "=" + clWDEncode.sEncodePOST(this.m_oObjetTable.m_sAliasTable);
			//	- contexte d'execution (specifique)
			sRequete += "&" + this.m_oSegment.m_nDebut + "=" + this.m_oSegment.m_nTaille;
			//	- Ordre de tri (specifique)
			if (this.m_nColonneTri >= 0)
			{
				// La syntaxe de la requete de tri est maintenant de la forme WD_TABLE_COL_TRI_=[-]NumeroColonne
//				sRequete += "&" + this.m_nColonneTri + "=" + (bTriCroissant ? "1" : "0");
				sRequete += "&" + this.sParametreAjax_ColTri + "=" + (bTriCroissant ? "" : "-") + this.m_nColonneTri;
			}
		}
		else
		{
			sRequete = this.sCommandeAjax_ParCourtAP + "=" + clWDEncode.sEncodePOST(this.m_oObjetTable.m_sAliasTable);
		}

		// Ajoute la position du parcours en AP si besoin
		sRequete += this.sConstuitAWP(false);

		//	- Cle de la ligne si disponible (Table/ZRs AWP)
		if (sCleEnreg.length > 0)
		{	// On doit encode la cle car elle peut contenir des + et des = qui sont decode/remplace lors de la reception du POST
			sRequete += "&" + this.sParametreAjax_CleEnreg_Pos + "=" + nCleEnregPos;
			sRequete += "&" + this.sParametreAjax_CleEnreg + "=" + clWDEncode.sEncodePOST(sCleEnreg);
		}

		// On renvoie la requete creee
		return sRequete;
	},

	// Construit la le debut de la requete pour la modification dans une table/ZR
	// tabContenu :	- Valeurs de la ligne du cache pour les tables
	//				- Champs de la ligne pour les ZR
	sConstuitRequeteModifLigne:function (bZR, tabContenu, sCleEnreg)
	{
		//	- Commande AJAX (non specifique)
		var sRequete = this.sCommandeAjax_ModifTable + "=" + clWDEncode.sEncodePOST(this.m_oObjetTable.m_sAliasTable);
		//	- Numero de ligne
		sRequete += "&" + "LIGNE" + "=" + this.m_nLigne;
		//	- Cle de la ligne si disponible (Table/ZRs AJAX)
		if (sCleEnreg.length > 0)
		{	// On doit encode la cle car elle peut contenir des + et des = qui sont decode/remplace lors de la reception du POST
			sRequete += "&" + this.sParametreAjax_CleEnreg + "=" + clWDEncode.sEncodePOST(sCleEnreg);
		}

		// Ajoute la position du parcours en AP si besoin
		sRequete += this.sConstuitAWP(false);

		// Valeurs (Specifiques)
		if (bZR)
		{
			var sValeur = clWDAJAXMain.sConstruitValeurPage(tabContenu);
			if(sValeur!="")
				sRequete += "&" + sValeur;
		}
		else
		{
			var tabParamValeur = new Array(tabContenu.length);
			var i;
			var nLimiteI = tabContenu.length;
			for (i = 0; i < nLimiteI; i++)
			{
				if ((tabContenu[i].m_nValeur !== undefined) && (tabContenu[i].m_nValeur !== -1))
					tabParamValeur[i] = "C" + i + "=" + tabContenu[i].m_nValeur;
				else
					tabParamValeur[i] = "C" + i + "=" + clWDEncode.sEncodePOST(tabContenu[i].m_sValeur);
			}
			sRequete += "&" + tabParamValeur.join("&");
		}

		// On renvoie le debut de la requete creee
		return sRequete;
	},

	// Construit la requete pour la selection dans une table
	sConstuitRequeteSelection:function (nColonneLien, nDebut, nNombre, sCleEnreg)
	{
		//	- Detail
		var sRequete = this.sParametreAjax_Debut + "=" + nDebut + "&" + this.sParametreAjax_Nombre + "=" + nNombre;
		//	- Colonne lien si besoin : On met le parametre en indic WL pour detecter les problemes dans le serveur
		sRequete += "&" + this.sParametreAjax_ColonneLien + "=" + (nColonneLien != -1 ? nColonneLien + 1: "");
		//	- Cle de la ligne si disponible (Table/ZRs AJAX)
		if (sCleEnreg.length > 0)
		{	// On doit encode la cle car elle peut contenir des + et des = qui sont decode/remplace lors de la reception du POST
			sRequete += "&" + this.sParametreAjax_CleEnreg + "=" + clWDEncode.sEncodePOST(sCleEnreg);
		}

		// Ajoute la position du parcours en AP si besoin
		sRequete += this.sConstuitAWP(true);

		// On renvoie la requete creee
		return sRequete;
	},

	// Valeur par defaut des variables : mis dans le prototype
	EnvoiRequete:function (oSegment, nColonneTri, bTriCroissant, nCleEnregPos, sCleEnreg)
	{
		// Nombre de lignes demandes
		this.m_oSegment = oSegment;
		// Tri demande
		this.m_nColonneTri = nColonneTri;

		// On demande les (nFin - nDebut) lignes a partir de nDebut
		this.m_nIdRequeteAJAX = clWDAJAXMain.AJAXRecupereLignesTable(this, this.sConstuitRequeteLignesTable(bTriCroissant, nCleEnregPos, sCleEnreg));

		// On s'ajoute au cache si il n'y a pas d'erreur
		if (this.m_nIdRequeteAJAX === false)
		{
			delete this.m_nIdRequeteAJAX;
			return;
		}
		else
		{
			this.m_oObjetTable.m_oCache.m_tabRequetes.push(this);
		}
	},

	EnvoiRequeteRecherche:function (nNombre, nMarge, nColonneTri, sValeur)
	{
		// Tri demande
		this.m_nColonneTri = nColonneTri;

		// On demande les (nFin - nDebut) lignes a partir de nDebut
		this.m_nIdRequeteAJAX = clWDAJAXMain.AJAXRecupereLignesTable(this, this.sConstuitRequeteRechercheTable(nNombre, nMarge, sValeur));

		// On s'ajoute au cache si il n'y a pas d'erreur
		if (this.m_nIdRequeteAJAX === false)
		{
			delete this.m_nIdRequeteAJAX;
			return;
		}
		else
		{
			this.m_oObjetTable.m_oCache.m_tabRequetes.push(this);
		}
	},

	// Envoi une requete de modification d'une ligne de table/ZR
	// tabContenu :	- Valeurs de la ligne du cache pour les tables
	//				- Champs de la ligne pour les ZR
	EnvoiRequeteModifLigne:function (nLigneAbsolue, bZR, tabContenu, sCleEnreg)
	{
		this.m_nLigne = nLigneAbsolue;

		// On demande les (nFin - nDebut) lignes a partir de nDebut
		this.m_nIdRequeteAJAX = clWDAJAXMain.AJAXRecupereLignesTable(this, this.sConstuitRequeteModifLigne(bZR, tabContenu, sCleEnreg));

		// On s'ajoute au cache si il n'y a pas d'erreur
		if (this.m_nIdRequeteAJAX === false)
		{
			delete this.m_nIdRequeteAJAX;
			return;
		}
		else
		{
			this.m_oObjetTable.m_oCache.m_tabRequetes.push(this);
		}
	},

	EnvoiRequeteSelection:function (nColonneLien, nDebut, nNombre, sCleEnreg)
	{
		var bSubmit = nColonneLien != -1 ? (this.m_oObjetTable.nColonneLien(nColonneLien) == 2) : false;
		// On demande les (nFin - nDebut) lignes a partir de nDebut
		this.m_nIdRequeteAJAX = clWDAJAXMain.AJAXRecupereLignesTableSelection(this, this.sConstuitRequeteSelection(nColonneLien, nDebut, nNombre, sCleEnreg), bSubmit);

		// On s'ajoute au cache si il n'y a pas d'erreur
		if (this.m_nIdRequeteAJAX === false)
		{
			delete this.m_nIdRequeteAJAX;
			return;
		}
		else
		{
			this.m_oObjetTable.m_oCache.m_tabRequetes.push(this);
		}
	},

	// Traite la reponse du serveur
	ActionListe:function (oXMLLignes)
	{
		// Si ona ete supprime : notre pointeur sur la table est null
		if (!this.m_oObjetTable)
		{
			return;
		}

		// Puis demande au cache de nous supprimer en sauvant notre pointeur sur la table car celui-ci va etre mis a null
		var oObjetTable = this.m_oObjetTable;
		this.SupprimeRequete(false);

		// Remplis le cache et met a jour l'affichage
		if(oObjetTable.bActionListe(oXMLLignes))
		{
			// Et on demande la MAJ de la ligne si besoin
			if (this.m_nLigne > -1)
			{
				oObjetTable.MAJLigne(this.m_nLigne);
			}
		}
	},

	// Calcule si notre requete est encore valide
	bEstValide:function ()
	{
		// On calcule la difference avec la date courante : limite => 10 secondes
		return ((new Date()).getTime() - this.m_oDate.getTime() <= 10000);
	},

	// Se supprime soit meme
	SupprimeRequete:function (bReinitTable)
	{
		// Sauve le pointeur sur la table
		var oObjetTable = this.m_oObjetTable;
		// Se libere
		oObjetTable.m_oCache.SupprimeRequete(this);

		// Et reinit la table au besoin
		if (bReinitTable)
		{
			// On ne le fait pas immediatement pour ne pas flooder le serveur
			oObjetTable.nSetTimeoutTable("Reinit", 2000);
		}
	}
};

// Classe representant une ligne de cache modifiee
function WDTableLigneModifie (oCacheLigne)
{
	// Sauve la ligne de cache
	this.m_oCacheLigne = oCacheLigne;

	// Initialise le tableau des valeurs modifiees
	this.m_tabValeursChaine = new Array(oCacheLigne.m_tabValeurs.length);
	// Et le tableau des valeurs entieres (Combo/Interrupteur)
	this.m_tabValeursEntier = new Array(oCacheLigne.m_tabValeurs.length);
};

WDTableLigneModifie.prototype =
{
	bLigneVirtuelle:		true,

	// Recupere le contenu d'une cellule
	sGetCellule:function (nColonne, bPourEntier)
	{
		if (this.m_tabValeursChaine[nColonne])
			return bPourEntier ? this.m_tabValeursEntier[nColonne] : this.m_tabValeursChaine[nColonne];
		else
			return this.m_oCacheLigne.sGetCellule(nColonne, bPourEntier);
	},

	// Recupere la cle d'un enreg
	sGetCleEnreg:function ()
	{
		return this.m_oCacheLigne.sGetCleEnreg();
	},

	// Met le contenu dans une ligne du HTML
	bMAJLigne:function (oObjetTable, nLigneAbsolue, sAliasLigne, sDebutAliasCellule)
	{
		// Met la valeur depuis la ligne en cache
		this.m_oCacheLigne.bMAJLigne(oObjetTable, nLigneAbsolue, sAliasLigne, sDebutAliasCellule);

		var tabValeurs = this.m_tabValeursChaine;

		// Puis ecrit nos valeurs par dessus
		var i = 0;
		var nLimiteI = tabValeurs.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if (tabValeurs[i] !== undefined)
			{
				var oValeur = tabValeurs[i];
				// Remplit la cellule
				oObjetTable.RemplitCellule(oGetId(sDebutAliasCellule + i), tabValeurs[i], nLigneAbsolue, i);
			}
		}
		return true;
	},

	// Indique que l'on a change la valeur de la colonne donnee
	ChangementValeur:function (nColonne, sValeur, nValeur)
	{
		if (nColonne < this.m_tabValeursChaine.length)
		{
			this.m_tabValeursChaine[nColonne] = sValeur;
			this.m_tabValeursEntier[nColonne] = nValeur;
		}
	},

	// Valide les changements fait dans la ligne
	ValideChangement:function (oTableCache, nLigneAbsolue)
	{
		var bChangement = false;

		var tabValeursChaine = this.m_tabValeursChaine;
		var tabValeursEntier = this.m_tabValeursEntier;

		// Place les valeurs dans la ligne du cache
		var i = 0;
		var nLimiteI = tabValeursChaine.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if (tabValeursChaine[i] !== undefined)
			{
				bChangement |= this.m_oCacheLigne.bChangementValeur(i, tabValeursChaine[i], tabValeursEntier[i]);
			}
		}

		// Construit une requete pour le serveur s'il y a eu des changement
		if (bChangement)
		{
			oTableCache.CreeRequeteModifLigne(nLigneAbsolue, false, this.m_oCacheLigne.m_tabValeurs);
		}
	}
};

// Classe representant une ligne de cache
function WDTableCacheLigne (oXMLLigne, oObjetTable)
{
	// Si on a un attribut de style de la ligne : on le stocke
//	this.m_sStyleCouleur = this.sTransformeCouleur(oXMLLigne, false);
//	this.m_sStyleCouleurFond = this.sTransformeCouleur(oXMLLigne, true);
	this.m_sStyleCouleur = clWDAJAXMain.sXMLGetAttributSafe(oXMLLigne, this.XML_STYLE_COULEUR);
	this.m_sStyleCouleurFond = clWDAJAXMain.sXMLGetAttributSafe(oXMLLigne, this.XML_STYLE_COULEURF);
	this.m_sCleEnreg = clWDAJAXMain.sXMLGetAttributSafe(oXMLLigne, this.XML_CLEENREG);

	// La valeurs
	var oXMLLigneChildNodes = oXMLLigne.childNodes
	var nLimiteI = oXMLLigneChildNodes.length;
	var tabValeurs = new Array(nLimiteI);
	var i;
	for (i = 0; i < nLimiteI; i++)
	{
		// Optim ???
		var oXMLCellule = oXMLLigneChildNodes[i];
		var oXMLCelluleChildNodes = oXMLCellule.childNodes;

		var oCellule = new Object()

		// Si on a un attribut de style de la cellule : on le stocke
//		oCellule.m_sStyleCouleur = this.sTransformeCouleur(oXMLCellule, false);
//		oCellule.m_sStyleCouleurFond = this.sTransformeCouleur(oXMLCellule, true);
		oCellule.m_sStyleCouleur = clWDAJAXMain.sXMLGetAttributSafe(oXMLCellule, this.XML_STYLE_COULEUR);
		oCellule.m_sStyleCouleurFond = clWDAJAXMain.sXMLGetAttributSafe(oXMLCellule, this.XML_STYLE_COULEURF);

		// Si on est dans le cas d'une ZR on lit la balise millieu
		if ((oXMLCellule.childNodes.length == 3) && (oXMLCellule.childNodes[1].nodeName == WDAJAXMain.prototype.XML_CHAMP_LIGNES_LIGNE_CORPS))
		{
			//assert(this.m_nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_ZONEREPETE)
			oCellule.m_sValeur = clWDAJAXMain.sXMLGetValeur(oXMLCellule.childNodes[1]);
		}
		else
		{
			oCellule.m_sValeur = clWDAJAXMain.sXMLGetValeur(oXMLCellule);

			// Proprietes specifiques
			switch (oObjetTable.nColonneType(i))
			{
			// Combo
			case WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO:
				var sValeur = oCellule.m_sValeur;
				var tabOptions = oObjetTable.tabColonneCombo(i);
				var j;
				var nLimiteJ = tabOptions.length;
				for (j = 0; j < nLimiteJ; j++)
				{
					if (sValeur == tabOptions[j])
					{
						oCellule.m_nValeur = j;
						break;
					}
				}
				break;

			// Interrupteur
			case WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR:
				oCellule.m_nValeur = parseInt(oCellule.m_sValeur);
				break;

			// Image
			case WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE:
				break;

			// Autres
			default:
				// Et fait comme le champ de saisie => Pas de break
			// Saisie
			case WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE:
				break;
			}
		}
		
		tabValeurs[i] = oCellule;
	}
	this.m_tabValeurs = tabValeurs;
};

WDTableCacheLigne.prototype =
{
//	XML_STYLE:			"STYLE",
	XML_STYLE_COULEUR:	"COULEUR",
	XML_STYLE_COULEURF:	"COULEURFOND",
	XML_CLEENREG:		"CLEENREG",
	m_oVide:			{ m_sValeur:"", m_sStyleCouleur:"", m_sStyleCouleurFond:"", m_nValeur:-1, m_sCleEnreg:"" },
	m_nValeur:			-1,

	// Recupere le contenu d'une cellule
	sGetCellule:function (nColonne, bPourEntier)
	{
		var oValeur = (nColonne < this.m_tabValeurs.length) ? this.m_tabValeurs[nColonne] : this.m_oVide;
		return bPourEntier ? oValeur.m_nValeur : oValeur.m_sValeur;
	},

	// Recupere la cle d'un enreg
	sGetCleEnreg:function ()
	{
		return this.m_sCleEnreg;
	},

	// Transforme une couleur si besoin
	sTransformeCouleur:function (oXML, bCouleurFond)
	{
		var sCouleur = clWDAJAXMain.sXMLGetAttributSafe(oXML, bCouleurFond ? this.XML_STYLE_COULEURF : this.XML_STYLE_COULEUR);
		// Si la couleur est vide : force transparent ou noir
		return sCouleur.length > 0 ? sCouleur : "";
	},

	// Met le contenu du cache dans une ligne du HTML
	bMAJLigne:function (oObjetTable, nLigneAbsolue, sAliasLigne, sDebutAliasCellule)
	{
		// Calcule si la ligne est sélectionnée
		var bSelected = (oObjetTable.nLigneSelectionne(nLigneAbsolue) != -1);

		// Met le style dans la ligne
		var oLigne = oGetId(sAliasLigne);
		this.SetCouleurObjet(oLigne, bSelected ? this.m_oVide : this);

		var tabValeurs = this.m_tabValeurs;

		// On parcours les valeur pour les mettre dans le HTML
		var i = 0;
		var nLimiteI = tabValeurs.length;
		var oCellule = oGetId(sDebutAliasCellule + i);
		while (oCellule || (i < nLimiteI))
		{
			if (oCellule)
			{
				var oValeur = (i < nLimiteI) ? tabValeurs[i] : this.m_oVide;
				// Remplit la cellule
				oObjetTable.RemplitCellule(oCellule, oValeur.m_sValeur, nLigneAbsolue, i);

				oCellule = oCellule.parentNode;

				// Et met son style
				this.SetCouleurObjet(oCellule, bSelected ? this.m_oVide : oValeur);
			}

			// Et passe a la suivante
			oCellule = oGetId(sDebutAliasCellule + (++i));
		}
		return true;
	},

	// Defini la couleur d'un objet
	SetCouleurObjet:function (oObjet, oCouleur)
	{
		// Recupere les objets de manipulation du style
		var oStyle = oObjet.style;
		var oCurrentStyle = _JGCS(oObjet);

		// Couleur
		if (oStyle.color != oCouleur.m_sStyleCouleur)
		{
//			if (oCouleur.m_sStyleCouleur == "")
//			{
//				delete oStyle.color;
//			}
//			else
//			{
				oStyle.color = oCouleur.m_sStyleCouleur;
//			}
		}

		// Couleur de fond
		if (oStyle.backgroundColor != oCouleur.m_sStyleCouleurFond)
		{
//			if (oCouleur.m_sStyleCouleurFond == "")
//			{
//				delete oStyle.backgroundColor;
//			}
//			else
//			{
				oStyle.backgroundColor = oCouleur.m_sStyleCouleurFond;
//			}
		}
	},

	// Indique que l'on a change la valeur de la colonne donnee
	bChangementValeur:function (nColonne, sValeur, nValeur)
	{
		var tabValeurs = this.m_tabValeurs;

		// Si la colonne existe et change
		if ((nColonne < tabValeurs.length) && !(tabValeurs[nColonne].m_sValeur === sValeur))
		{
			tabValeurs[nColonne].m_sValeur = sValeur;
			tabValeurs[nColonne].m_nValeur = nValeur;
			return true;
		}
		return false;
	}
};

// Representation d'un segment de lignes entre 0 et nMax
function Segment (nDebut, nTaille, nMax)
{
	this.m_nDebut = nDebut;
	this.m_nTaille = nTaille;

	// On commence forcement a zero
	if (this.m_nDebut < 0)
	{	// Sauf que -1, -1 est autorise
		if ((this.m_nDebut != -1) && (this.m_nTaille != -1))
		{
			// Ainsi que zero en taille
			if (this.m_nTaille != 0) this.m_nTaille += this.m_nDebut;
			this.m_nDebut = 0
		}
	}

	// Et on se limite a nMax lignes si besoin
	if (nMax >= 0)
	{
		if (this.m_nDebut >= nMax)
		{
			// Se vide
			delete this.m_nDebut;
			delete this.m_nTaille;
			return;
		}

		if (this.m_nDebut + this.m_nTaille > nMax)
		{
			// Limite le nombre de lignes
			if (this.m_nTaille != 0) this.m_nTaille = nMax - this.m_nDebut;
		}
	}
};

Segment.prototype =
{
	// Valeur par defaut
	m_nDebut:		-1,
	m_nTaille:		0,

	// Calcule l'intersection avec un autre segment, se modifie pour ne contenir que la partie non interceptee
	// Renvoie vrai si le segment resultat est vide
	// On ne peut englober un precedent segment
	bIntersecte:function (oSegment)
	{
		// Si vide => Fini
		if ((this.m_nDebut == -1) || (this.m_nTaille <= 0))
		{
			return true;
		}

		// Si toutes les lignes dans l'autre segment
		if (oSegment && (oSegment.m_nTaille == 0))
		{
			// Se vide
			delete this.m_nDebut;
			delete this.m_nTaille;
			return true;
		}

		// Si l'autre est vide=> Fini
		if ((!oSegment) || (oSegment.m_nDebut == -1) || (oSegment.m_nTaille <= 0))
		{
			return false;
		}
		// Si on est dedans => Fini
		if ((this.m_nDebut >= oSegment.m_nDebut) && (this.m_nDebut + this.m_nTaille <= oSegment.m_nDebut + oSegment.m_nTaille))
		{
			// Se vide
			delete this.m_nDebut;
			delete this.m_nTaille;
			return true;
		}
		// Si on est avant avec intersection
		else if ((this.m_nDebut < oSegment.m_nDebut) && (this.m_nDebut + this.m_nTaille > oSegment.m_nDebut))
		{
			// Se vide du surplus
			this.m_nTaille = oSegment.m_nDebut - this.m_nDebut;
			return false;
		}
		// Si on est apres avec intersection
		else if ((this.m_nDebut >= oSegment.m_nDebut) && (this.m_nDebut < oSegment.m_nDebut + oSegment.m_nTaille) && (this.m_nDebut + this.m_nTaille > oSegment.m_nDebut + oSegment.m_nTaille))
		{
			// Deplace le debut
			this.m_nTaille -= oSegment.m_nDebut + oSegment.m_nTaille - this.m_nDebut;
			this.m_nDebut = oSegment.m_nDebut + oSegment.m_nTaille;
			return false;
		}
		return false;
	},

	// Indique si le segment est vide
	bEstVide:function ()
	{
//		return ((this.m_nTaille <= 0) || ((this.m_nDebut < 0) && (this.m_nDebut + this.m_nTaille <= 0)))
		return ((this.m_nTaille < 0) || ((this.m_nDebut < 0) && (this.m_nDebut + this.m_nTaille <= 0)))
	}
};

// Classe representant un le cache
function WDTableCache (oObjetTable)
{
	// On sauve l'objet qui represente la table que l'on manipule
	this.m_oObjetTable = oObjetTable;

	// Recalcule les marges
	this.RecalculeMarges(oObjetTable.m_nNbLignesPage, oObjetTable.m_nPagesMargeMin, oObjetTable.m_nPagesMargeMax, oObjetTable.m_nPagesMargeRequete);

	// Et aucunes lignes de cache
	this.m_tabLignes = new Array();

	// Le tableaux des zones demandes
	this.m_tabRequetes = new Array();
};

WDTableCache.prototype =
{
	m_nDebutCache:		-1,		// Au debut on a un cache vide
	XML_LIGNE:			"LIGNE",
	XML_LIGNE_NUMERO:	"NUMERO",

	// Recalcule le nombre d'elements de marge dans le cache par rapport a la position de la premiere ligne affichee
	RecalculeMarges:function (nNbLignesPage, nPagesMargeMin, nPagesMargeMax, nPagesMargeRequete)
	{
		// Si pas de limite au nombre de ligne => Fini
		if (nNbLignesPage == 0)
			return;

		// Nombre minimum de marge dans le cache avant de refaire une demande
		this.m_nLignesMargeMinAvant = nPagesMargeMin * nNbLignesPage;
		this.m_nLignesMargeMinTaille = (nPagesMargeMin * 2 + 1) * nNbLignesPage + 2;
		// Nombre maximum de marge dans le cache avant de virer du cache
		this.m_nLignesMargeMaxAvant = nPagesMargeMax * nNbLignesPage;
		this.m_nLignesMargeMaxApres = (nPagesMargeMax + 1) * nNbLignesPage + 2;
		// Marge mimimum lors de requetes de lignes dans le cache
		this.m_nLignesMargeRequeteAvant = Math.min(nPagesMargeRequete * nNbLignesPage, this.m_nLignesMargeMaxAvant);
		this.m_nLignesMargeRequeteTaille = Math.min((nPagesMargeRequete * 2 + 1) * nNbLignesPage + 2, this.m_nLignesMargeMaxAvant + this.m_nLignesMargeMaxApres);
	},

	// Remplit le cache avec des donnees si besoin
	RemplitCache:function (nPosition, bForce, nColonneTri, bTriCroissant)
	{
		// Force si on a un tri
		if (nColonneTri > -1)
		{
			// Invalide completement le cache ce qui equivant a bForce = ture
			this.m_tabLignes.length = 0;
			delete this.m_nDebutCache;
		}

		var oCacheRequete;
		// Si on est dans le cas sans limite de lignes : on demande toujours toutes les lignes
		if (this.m_oObjetTable.m_nNbLignesPage == 0)
		{
			oCacheRequete = new Segment(0, 0);
		}
		else
		{
			// On demande par defaut les valeurs autour de la position donnee en se limitant au nombre de lignes de la table
			var oCacheMin = new Segment(Math.max(0, nPosition - this.m_nLignesMargeMinAvant), this.m_nLignesMargeMinTaille, bForce ? -1 : this.m_oObjetTable.m_nNbLignes);
			// On demande un mimimun de lignes apres la fin pour el cas ou celle-ci change
			oCacheRequete = new Segment(Math.max(0, nPosition - this.m_nLignesMargeRequeteAvant), this.m_nLignesMargeRequeteTaille, bForce ? -1 : this.m_oObjetTable.m_nNbLignes + this.m_nLignesMargeMinTaille);

			// Si le cache est non vide, et que l'on ne force pas on regarde ce qu'il manque et on le demande
			if ((this.m_nDebutCache != -1) && (!bForce))
			{
				// Si on a assez de lignes dans le cache
				var oCache = new Segment(Math.max(0, this.m_nDebutCache), this.m_tabLignes.length);
				if (oCacheMin.bIntersecte(oCache))
				{
					return;
				}

				// Puis on regarde si notre requete intersecte les precedentes demandes
				if (this.bIntersecteAnciennesRequetes(oCacheMin, nColonneTri > -1))
				{
					return;
				}

				// On intersecte la requete avec le cache courant
				oCacheRequete.bIntersecte(oCache);
			}
		}

		// Si il n'y a pas intersection : on intersecte la requete minimale avec le cache actuel et les requetes en cours pour avori la requete minimale
		if (!this.bIntersecteAnciennesRequetes(oCacheRequete, nColonneTri > -1))
		{
			// En mode AWP : Trouve l'enreg le plus proche dans le cache pour se positionner dessus
			var nCleEnregPos = -1;
			var sCleEnreg = "";
			if (clWDAJAXMain.m_bPageAWP)
			{
				if ((this.m_nDebutCache >= 0) && (this.m_oObjetTable.m_nNbLignesPage != 0))
				{
					// Si le debut du cache est avant la zone en requete
					if (this.m_nDebutCache < oCacheRequete.m_nDebut)
					{
						// On prend le plus proche dans le cache
						nCleEnregPos = Math.min(this.m_nDebutCache + this.m_tabLignes.length - 1, oCacheRequete.m_nDebut - 1);
					}
					else
					{
						nCleEnregPos = Math.max(this.m_nDebutCache, oCacheRequete.m_nDebut + oCacheRequete.m_nTaille);
					}
					sCleEnreg = this.sGetCleEnregLigne(nCleEnregPos);
				}

				// Si on n'a pas de positionnement : utilise la cle fournis pour la position de debut
				if ((this.m_oObjetTable.m_nDebut > -1) && (this.m_oObjetTable.m_sCleDebut) &&
					((nCleEnregPos == -1) || (Math.abs(this.m_oObjetTable.m_nDebut - oCacheRequete.m_nDebut) < Math.abs(this.m_oObjetTable.m_nDebut - nCleEnregPos))))

				{
					nCleEnregPos = this.m_oObjetTable.m_nDebut;
					sCleEnreg = this.m_oObjetTable.m_sCleDebut;
				}
			}

			// Cree la requete qui va bien
			this.CreeRequete(oCacheRequete, nColonneTri, bTriCroissant, nCleEnregPos, sCleEnreg);
		}
	},

	// Calcule l'intersection d'une requete avec les requetes precedentes
	// Renvoie vrai si la requetes resultat est vide
	bIntersecteAnciennesRequetes:function (oRequete, bForce)
	{
		// Vire les anciennes requetes qui sont en timeout
		this.PurgeAnciennesRequetes(bForce);

		// Regarde si il y a intersection avec de precedente requetes
		var i = 0;
		var nLimiteI = this.m_tabRequetes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			// Si la requete intersecte le segment et devient vide
			if (oRequete.bIntersecte(this.m_tabRequetes[i].m_oSegment))
			{
				return true;
			}

			// Ne gere pas le cas de l'englobement
		}

		return oRequete.bEstVide();
	},

	// Verifie si il y a besoin d'envoyer une requete et la cree si besoin
	CreeRequete:function(oCacheRequete, nColonneTri, bTriCroissant, nCleEnregPos, sCleEnreg)
	{
		// Cree une requete que l'on envoie
		var oRequete = new WDCacheRequete(this.m_oObjetTable);

		oRequete.EnvoiRequete(oCacheRequete, nColonneTri, bTriCroissant, nCleEnregPos, sCleEnreg);

		// Decide si il faut afficher ou non le picto de travail
		this.m_oObjetTable.AfficheLoading();
	},

	// Creer une requete de recherche
	CreeRequeteRecherche:function(nColonneTri, sValeur)
	{

		// Cree une requete que l'on envoie
		var oRequete = new WDCacheRequete(this.m_oObjetTable);

		// On ne cree pas la meme requete si on est en nombre de ligne limite par page ou pas
		// Si on est en nombre de ligne illimite this.m_nLignesMargeRequeteAvant n'existe pas
		oRequete.EnvoiRequeteRecherche(this.m_oObjetTable.m_nNbLignesPage, this.m_nLignesMargeRequeteAvant ? this.m_nLignesMargeRequeteAvant : 0, nColonneTri, sValeur);

		// Decide si il faut afficher ou non le picto de travail
		this.m_oObjetTable.AfficheLoading();
	},

	// Creer une requete de modification de ligne
	// tabContenu :	- Valeurs de la ligne du cache pour les tables
	//				- Champs de la ligne pour les ZR
	CreeRequeteModifLigne:function(nLigneAbsolue, bZR, tabContenu)
	{
		// Cree une requete que l'on envoie
		var oRequete = new WDCacheRequete(this.m_oObjetTable);

		// Envoi la requete de recherche
		oRequete.EnvoiRequeteModifLigne(nLigneAbsolue, bZR, tabContenu, this.sGetCleEnregLigne(nLigneAbsolue));

		// Decide si il faut afficher ou non le picto de travail
		this.m_oObjetTable.AfficheLoading();
	},

	// Creer une requete de selection de ligne
	CreeRequeteSelection:function (nColonneLien)
	{
		// Cree une requete que l'on envoie
		var oRequete = new WDCacheRequete(this.m_oObjetTable);

		// Envoi la requete de recherche
		oRequete.EnvoiRequeteSelection(nColonneLien, this.m_nDebutCache, this.m_tabLignes.length, this.sGetCleEnregLigne(this.m_nDebutCache));

		// Normalement notre parent affiche le masque de loading
//		this.m_oObjetTable.AfficheLoading();
	},

	// Calcule la cle d'enreg a utiliser
	sGetCleEnregLigne:function (nLigneAbsolue)
	{
		// Recupere la cle de l'enreg si besoin
		// Si la ligne est dans le cache (Normalement c'est toujours le cas)
		if (this.bDansPlageCache(nLigneAbsolue))
		{
			var oLigne = this.m_tabLignes[nLigneAbsolue - this.m_nDebutCache];
			if (oLigne)
			{
				return oLigne.sGetCleEnreg();
			}
		}
		return "";
	},

	// Vire les anciennes requetes qui sont en timeout
	PurgeAnciennesRequetes:function (bForce)
	{
		var i = 0;
		var nLimiteI = this.m_tabRequetes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if ((this.m_tabRequetes[i].bEstValide() == false) || bForce)
			{
				delete this.m_tabRequetes[i].m_oObjetTable;
				// On commence par rechercher la requete
				var oRequete = clWDAJAXMain.GetWDAJAXRequete(this.m_tabRequetes[i].m_nIdRequeteAJAX);
				// Si on trouve la requete, on l'annule
				if (oRequete!=null) oRequete.Annule();
				delete oRequete;

				// Vire la requete du tableau
				this.m_tabRequetes.splice(i, 1);
				// Ne pas oublier le -- sinon on saute une requete
				i--;
				nLimiteI--;
			}
		}

		// Decide si il faut afficher ou non le picto de travail
		this.m_oObjetTable.AfficheLoading();
	},

	// Supprime une requete
	SupprimeRequete:function (oRequete)
	{
		// Recherche la requete selon un critere de date et de numero de requete AJAX
		var i = 0;
		var nLimiteI = this.m_tabRequetes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if ((this.m_tabRequetes[i].m_oDate.getTime() == oRequete.m_oDate.getTime()) && (this.m_tabRequetes[i].m_nIdRequeteAJAX == oRequete.m_nIdRequeteAJAX))
			{
				delete this.m_tabRequetes[i].m_oObjetTable;
				// Vire la requete du tableau
				this.m_tabRequetes.splice(i, 1);
				// Et fini
				return;
			}
		}

		// Decide si il faut afficher ou non le picto de travail
		this.m_oObjetTable.AfficheLoading();
	},

	// Indique si une requete de tri existe dans le cache
	bAvecRequeteTri:function ()
	{
		// Recherche la requete selon un critere de date et de numero de requete AJAX
		var i = 0;
		var nLimiteI = this.m_tabRequetes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if (this.m_tabRequetes[i].m_nColonneTri > -1)
			{
				// On a trouve une requete
				return true;
			}
		}
		return false;
	},

	// Decide si la ligne numero nLigne a sa place dans le cache
	// Si non => Renvoi -1
	// Si oui => Fait de la place eventuelle dans le cache et renvoi la place de cette ligne
	nPlaceLigneCache:function (nLigne)
	{
		// Si on est pas en nombre de ligne illimite on teste si la ligne est hors cache
		if (this.m_oObjetTable.m_nNbLignesPage > 0)
		{
			// Si la ligne est trop loin de la ligne courante => Fini
			if ((nLigne < this.m_oObjetTable.m_nDebut - this.m_nLignesMargeMaxAvant) || (nLigne >= this.m_oObjetTable.m_nDebut + this.m_nLignesMargeMaxApres))
			{
				return -1;
			}
		}

		// Si on n'a pas encore de cache : on l'initialise
		if (this.m_nDebutCache == -1)
		{
			this.m_nDebutCache = nLigne;
			//assert(this.m_tabLignes.length == 0);
			this.m_tabLignes.push(null);
			return 0;
		}

		// Sinon on agrandi le tableau au besoin
		// Si la ligne est avant le debut du cache
		if (nLigne < this.m_nDebutCache)
		{
			// Si on est "proche" du cache (=> Une partie du cache est
			// On ajoute un tableau vide au debut
			// Insere le nombre d'entre vides qui vont bien et les initialise a zero
			var i;
			var nLimiteI = this.m_nDebutCache - nLigne;
			for (i = 0; i < nLimiteI; i++)
			{
				this.m_tabLignes.unshift(null);
			}
//			this.m_tabLignes = (new Array(this.m_nDebutCache - nLigne)).concat(this.m_tabLignes);
			// Decale le debut
			this.m_nDebutCache = nLigne;
		}
		else if (nLigne >= this.m_nDebutCache + this.m_tabLignes.length)
		{	// Si la ligne est apres la fin
			var i;
			var nLimiteI = nLigne - this.m_nDebutCache - this.m_tabLignes.length + 1;
			for (i = 0; i < nLimiteI; i++)
			{
				this.m_tabLignes.push(null);
			}
//			this.m_tabLignes = this.m_tabLignes.concat(new Array(nLigne - this.m_nDebutCache - this.m_tabLignes.length + 1));
			// Pas de decalage du debut
		}
		else if ((nLigne - this.m_nDebutCache < this.m_tabLignes.length) && (this.m_tabLignes[nLigne - this.m_nDebutCache] != null))
		{	// La ligne est dans la place : on supprime par securite la ligne du cache
			delete this.m_tabLignes[nLigne - this.m_nDebutCache].m_tabValeurs;
			this.m_tabLignes[nLigne - this.m_nDebutCache] = null;
		}

		// Renvoie la distance au debut
		return nLigne - this.m_nDebutCache;
	},

	// Recoit des donnees du serveur et le met dans le cache et analysant le XML des donnees
	RemplitCacheLignes:function (oXMLLignes)
	{
		// Met a jour le cache avec chaque ligne
		var oXMLLigne = oXMLLignes.firstChild;
		while (oXMLLigne != null)
		{
			// Lit le numero de la ligne
			//assert(XMLLigne.nodeName == this.XML_LIGNE);
			var nLigne = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLigne, this.XML_LIGNE_NUMERO));

			// Decide si on doit mettre la ligne dans le cache et fait la place si necessaire dans celui-ci
			// On recoit le numero de position dans le cache (ou -1)
			var nPosCacheLigne = this.nPlaceLigneCache(nLigne);
			if (nPosCacheLigne != -1)
			{
				//assert(this.m_tabLignes[nPosCacheLigne] == null);
				// Ajoute la ligne dans le cache
				this.m_tabLignes[nPosCacheLigne] = new WDTableCacheLigne(oXMLLigne, this.m_oObjetTable);
			}

			// Passe a la ligne suivante
			oXMLLigne = oXMLLigne.nextSibling;
		}
	},

	// Vide le cache des donnees inutiles
	SupprimeLignesInutiles:function ()
	{
		// Si pas encore de cache => Fini
		if (this.m_nDebutCache == -1)
		{
			return;
		}

		// Si on est en nombre illimite de lignes : supprime les lignes qui sont apres la fin
		if (this.m_oObjetTable.m_nNbLignesPage == 0)
		{
			if (this.m_tabLignes.length > this.m_oObjetTable.m_nNbLignes)
			{
				this.m_tabLignes = this.m_tabLignes.slice(0, this.m_oObjetTable.m_nNbLignes);
			}
			return;
		}

		// Si tous le cache est avant la zone de marge => Supprime le cache
		if (this.m_nDebutCache + this.m_tabLignes.length <= this.m_oObjetTable.m_nDebut - this.m_nLignesMargeMaxAvant)
		{
			this.m_tabLignes.length = 0;
			delete this.m_nDebutCache;
			return;
		}

		// Si le cache est apres la zone de marge => Supprime le cache
		if (this.m_nDebutCache >= this.m_oObjetTable.m_nDebut + this.m_nLignesMargeMaxApres)
		{
			this.m_tabLignes.length = 0;
			delete this.m_nDebutCache;
			return;
		}

		// Si le debut du cache est avant la zone de marge (Et donc avec une zone de recouvrement au vu des test precedents) : supprime le cache en trop
		if (this.m_nDebutCache < this.m_oObjetTable.m_nDebut - this.m_nLignesMargeMaxAvant)
		{
			this.m_tabLignes = this.m_tabLignes.slice(this.m_oObjetTable.m_nDebut - this.m_nLignesMargeMaxAvant - this.m_nDebutCache);
			this.m_nDebutCache = this.m_oObjetTable.m_nDebut - this.m_nLignesMargeMaxAvant;
		}

		// Si on a trop de cache vers la fin (Et donc avec une zone de recouvrement au vu des test precedents) : supprime le cache en trop
		if (this.m_nDebutCache + this.m_tabLignes.length >= this.m_oObjetTable.m_nDebut + this.m_nLignesMargeMaxApres)
		{
			this.m_tabLignes = this.m_tabLignes.slice(0, this.m_nLignesMargeMaxApres);
		}
		// Si on des lignes qui n'existent plus
		if (this.m_nDebutCache + this.m_tabLignes.length > this.m_oObjetTable.m_nNbLignes)
		{
			this.m_tabLignes = this.m_tabLignes.slice(0, this.m_oObjetTable.m_nNbLignes - this.m_nDebutCache);
		}
	},

	// Met a jour la ligne donnee en fonction des elements dans le cache
	// nLigne est le numero de ligne, nLigneRelatif est le numero de ligne parmis les lignes affiches
	bMAJLigne:function (nLigne, nLigneRelatif)
	{
		// Si la ligne n'est pas dans le cache : fini
		if (!this.bDansPlageCache(nLigne))
		{
			return false;
		}

		// Si la ligne n'est pas dans le cache => fini aussi
		if (!this.m_tabLignes[nLigne - this.m_nDebutCache])
		{
			return false;
		}

		// Demande a la ligne de cache de mettre a jour l'affichage
		return this.m_tabLignes[nLigne - this.m_nDebutCache].bMAJLigne(this.m_oObjetTable, nLigne, this.m_oObjetTable.sGetTableId(nLigneRelatif), this.m_oObjetTable.sGetTableId(nLigneRelatif) + this.m_oObjetTable.ID_SEPARATEUR);
	},

	// Recupere le contenu d'une cellule
	sGetCellule:function (nLigneAbsolue, nColonne, bPourEntier)
	{
		// Si la ligne n'est pas dans le cache : fini
		if (!this.bDansPlageCache(nLigneAbsolue) || !this.m_tabLignes[nLigneAbsolue - this.m_nDebutCache])
		{
			return bPourEntier ? -1 : "";
		}

		// Demande a la ligne de cache
		return this.m_tabLignes[nLigneAbsolue - this.m_nDebutCache].sGetCellule(nColonne, bPourEntier);
	},

	// Indique si une ligne est dans la zone normalement couverte par le cache
	bDansPlageCache:function (nLigne)
	{
		// Le ligne ne doit pas etre avant ou apres
		return ((nLigne >= this.m_nDebutCache) && (nLigne < this.m_nDebutCache + this.m_tabLignes.length))
	},

	// Indique que l'on a change la valeur de la colonne donnee
	// nLigne est en valeur absolue
	ChangementValeur:function (nLigne, nLigneRelatif, nColonne, sValeur, nValeur)
	{
		// Si pas de cache => Fini c'est qu'il y a eu une erreur fatale => reset de la table
		if (this.m_nDebutCache == -1) return;

		// Trouve le numerode ligne dans le cache
		var nLigneCache = nLigne - this.m_nDebutCache;

		// Si la ligne est dans le cache on la modifie
		if ((nLigneCache <= this.m_tabLignes.length) && (this.m_tabLignes[nLigneCache] != null))
		{
			this.m_tabLignes[nLigneCache].ChangementValeur(nColonne, sValeur, nValeur);
		}
	},

	// Et notifie le serveur de la validation d'une ligne
	// nLigne est en valeur absolue
	ValideChangement:function (nLigneAbsolue)
	{
		var oLigneCache = this.m_tabLignes[nLigneAbsolue - this.m_nDebutCache];
		// Valide le changement sur la ligne virtuelle
		if (oLigneCache && oLigneCache.bLigneVirtuelle)
		{
			oLigneCache.ValideChangement(this, nLigneAbsolue);
		}
	},

	// Supprime les lignes virtuelles
	SupprimeLignesVirtuelles:function ()
	{
		// On parcours nos lignes de cache et supprime les lignes virtuelles
		var i;
		var nLimiteI = this.m_tabLignes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			var oLigne = this.m_tabLignes[i];
			// Si c'est une ligne virtuelle
			if (oLigne && oLigne.bLigneVirtuelle)
			{
				// Remet la ligne normale
				this.m_tabLignes[i] = oLigne.m_oCacheLigne;

				// Supprime les membres
				delete oLigne.m_oCacheLigne;
				delete oLigne.m_tabValeurs;
				delete oLigne;
			}
		}
	},

	// Cree la ligne virtuelle si besoin
	// nLigne est en valeur absolue
	CreeLigneVirtuelle:function (nLigneAbsolue)
	{
		// Calcule la position absolue dans le tableau
		var nLigneRelatifDebutCache = nLigneAbsolue - this.m_nDebutCache;
		if (this.m_tabLignes[nLigneRelatifDebutCache] && !this.m_tabLignes[nLigneRelatifDebutCache].bLigneVirtuelle)
		{
			this.m_tabLignes[nLigneRelatifDebutCache] = new WDTableLigneModifie(this.m_tabLignes[nLigneRelatifDebutCache]);
		}
	}
};

// Classe de base des colonnes
function WDColonne (oXMLColonne)
{
	// Propriete generiques
	this.m_bSaisissable = (clWDAJAXMain.sXMLGetAttributSafe(oXMLColonne, this.XML_SAISISSABLE) == "1");
	this.m_bVisible = (clWDAJAXMain.sXMLGetAttributSafe(oXMLColonne, this.XML_VISIBLE) == "1");
	this.m_sBulle = clWDAJAXMain.sXMLGetAttributSafeNull(oXMLColonne, this.XML_BULLE);

	// Proprietes specifiques
	this.m_nType = parseInt(clWDAJAXMain.sXMLGetAttributSafe(oXMLColonne, WDAJAXMain.prototype.XML_CHAMP_ATT_TYPE))
	switch (this.m_nType)
	{
	// Combo
	case WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO:
		// Tableaux des options
		this.tabOptions = new Array(oXMLColonne.childNodes.length)
		var tabOptions = this.tabOptions;

		// On parcourt les options de la combo
		var oXMLOption = oXMLColonne.firstChild;
		var nPos = 0;
		while(oXMLOption)
		{
			tabOptions[nPos] = clWDAJAXMain.sXMLGetValeur(oXMLOption);
			// Option suivante
			oXMLOption = oXMLOption.nextSibling;
			nPos++;
		}
		break;

	// Interrupteur
	case WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR:
		break;

	// Autres
	default:
		// Force le type
		this.m_nType = WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE;
		// Et fait comme le champ de saisie et de la champ image => Pas de break

	// Image
	case WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE:
		// Pas saisissable
		this.m_bSaisissable = false;
		// Et fait comme le champ de saisie => Pas de break

	// Saisie
	case WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE:
		// Gestion des liens
		var sTmp = clWDAJAXMain.sXMLGetAttributSafe(oXMLColonne, this.XML_LIEN);
		if (sTmp != "")
		{
			this.nLien = parseInt(sTmp);
			sTmp = clWDAJAXMain.sXMLGetAttributSafe(oXMLColonne, this.XML_ETATLIEN);
			if (sTmp != "")
			{
				this.nEtatLien = parseInt(sTmp);
			}
		}
		break;
	}
};

WDColonne.prototype =
{
	// Valeur par defaut des proprietes
	nLien:				0,		// Type de lien : sans
	nEtatLien:			0,		// Etat du lien : actif
	tabOptions:			new Array(),

	// Constantes
	XML_SAISISSABLE:	"SAISISSABLE",
	XML_VISIBLE:		"VISIBLE",
	XML_LIEN:			"LIEN",
	XML_ETATLIEN:		"ETATLIEN",
	XML_BULLE:			"BULLE"
};

// Classe manipulant la table
function WDTable (sAliasTable, sNomVariable, bSansLimite, nHauteurLigne, nPagesMargeMin, nPagesMargeMax, nTypeSelection, tabStyle, tabImgTri, tabImgRecherche)
{
	// Donnees de base
	this.m_sAliasTable = sAliasTable;		// Nom de la table
	this.m_sNomVariable = sNomVariable;		// Nom de la variable (= Nom de notre instance dans la page : pour utilisation dans un setTimeout)
	this.m_bSansLimite = bSansLimite;		// Si on limite le nombre de ligne dans la page
	this.m_nHauteurLigne = nHauteurLigne;
	// Si on est en nombre limite de ligne
	if (!this.m_bSansLimite)
	{
		this.m_nPagesMargeMin = nPagesMargeMin;		// Nombre minimum de lignes dans le cache avant la premiere ligne affiche (Ou apres la denriere)
		this.m_nPagesMargeMax = nPagesMargeMax;		// Nombre maximum de lignes coserve dans le cache avant la premiere ligne
		this.m_nPagesMargeRequete = parseInt((nPagesMargeMax + nPagesMargeMin) / 2);
	}
	this.m_nTypeSelection = nTypeSelection;		// Type de selection autorisee

	// Tableau des noms de styles
	this.m_tabStyle = tabStyle;

	// Images de tri et de recherche
	this.m_tabImgTri = tabImgTri;
	this.m_tabImgRecherche = tabImgRecherche;
	// Et prechargement
	var nImage;
	for (nImage in this.m_tabImgTri) { (new Image()).src = this.m_tabImgTri[nImage]; };
	for (nImage in this.m_tabImgRecherche) { (new Image()).src = this.m_tabImgRecherche[nImage]; };

	// Nombre d'initialisation
	this.m_nNbInits = 0;

	// Ne rien mettre ici qui doit etre remit a zero dans l'init
};

WDTable.prototype =
{
	// Donnees semi-dynamique
	m_nFacteurAscenseur:		1,			// Le facteur multiplicateur de la barre de defilement (Pour les tables tres tres grandes)
	// La limite des barres d'outils n'est pas la meme selon le navigateur
	// - IE			134217727	ou 0x7FFFFFF
	// - Firefox	71582788	ou 0x4444444
	m_nLimiteHauteur:			bIE ? 134217727 : 71582788,
	m_bRequeteDiffereeForce:	false,		// Debut de la prochaine requete : utilise par le systeme de requete differe
	m_nRequeteDiffereeTimeOutId:undefined,
	m_nColonneTrie:				-1,
	m_nColonneTriePre:			-1,			// Indication de tri que l'on a recu du serveur
	m_bSensTri:					true,
	m_bSensTriPre:				true,		// Indication de tri que l'on a recu du serveur
	m_nColonneRecherche:		-1,			// Colonne de recherche
	m_bFinTrouve:				false,		// Si la fin de la table est connue
	m_sCleParcourtAP:			"",			// Par defaut pas de cle pour le positionnement du parcour en arriere plan
	m_bRetourServeurSelection:	true,		// Par defaut on notifie le serveur de chaque selection
//	m_oFormRecherche:			undefined,
//	m_oImgOrigine:				undefined,
//	m_fOnMouseDown:				undefined,
//	m_fPCodeSelectionNav		undefined,
	m_bMasqueVisible:			false,
	m_nType:					WDAJAXMain.prototype.XML_CHAMP_TYPE_TABLE,

	XML_NOMBRE:					"NOMBRE",
	XML_FIN:					"FIN",
	XML_DEBUT:					"DEBUT",
	XML_CLEENREGAP:				"CLEENREGAP",
	XML_SELECTIONS:				"SELECTIONS",
	XML_COLONNES:				"COLONNES",
	XML_LIGNES:					"LIGNES",
	XML_SELECTION:				"SELECTION",
	XML_COLONNE:				"COLONNE",
	XML_ERREUR:					"ERREUR",
	XML_TRI:					"TRI",
	XML_SENS:					"SENS",
	XML_DESACTIVESERSEL:		"DESACTIVESERSEL",
//	XML_VALMEM:					"VALMEM",
	SELECTION_SANS:				0,
	SELECTION_SIMPLE:			1,
	SELECTION_MULTIPLE:			2,
	ID_SEPARATEUR:				"_",
	ID_SCROLLBAR:				"SB",
	ID_TABLEINTERNE:			"TB",
	ID_POSITION_PIXEL:			"POS",
	ID_CHARGEMENT_IMG:			"LOAD",
	ID_MASQUE:					"MASQUE",
	ID_MASQUETRANSPARENT:		"MASQUETR",
	ID_TITRE:					"TITRES",
	ID_TRI:						"TRI",
	SEL_SEPARATEUR:				";",

	// Fin de l'init (Apres le parcours du HTML)
	Init:function (nColonneTrie)
	{
		this.m_oDivPos = this.oGetTableId(this.ID_POSITION_PIXEL);

		// Pour le W3C : la gestion du scroll et du redimesionnement est faite ici par code
		var oPosPixel = this.m_oDivPos;
		var oTitrePosPixel = this.oGetTableId(this.ID_TITRE, this.ID_POSITION_PIXEL);
		if (oTitrePosPixel)
			this.m_oDivPos.parentNode.onscroll = function () { oTitrePosPixel.parentNode.scrollLeft = oPosPixel.parentNode.scrollLeft; };
		var oMasque = this.oGetTableId(this.ID_MASQUE);
		var oMasqueTr = this.oGetTableId(this.ID_MASQUETRANSPARENT);
		this.m_oDivPos.parentNode.onresize = function () { oMasque.style.width = oMasque.parentNode.clientWidth;oMasque.style.height = oMasque.parentNode.clientHeight;oMasqueTr.style.width = oMasqueTr.parentNode.clientWidth;oMasqueTr.style.height = oMasqueTr.parentNode.clientHeight; };

		// Fonction de defilement : uniquement si on a un ascenseur
		if (!this.m_bSansLimite)
		{
			// Fonction de defilement
			var oTmp = this;
			this.m_fScroll = function() { oTmp.DeplaceTable.call(oTmp); };

			// La scroll bar et le div positionnable
			this.m_oAscenseur = this.oGetTableId(this.ID_SCROLLBAR);
			this.m_oAscenseurParent = this.m_oAscenseur.parentNode;

			// Initialise la roulette
			this.InitRoulette(this.m_oDivPos.parentNode);
		}

		// Initialise le HTML de la ligne en le lisant du fichier Mais uniquement lors de la prmiere init
		// (Lors des suivante le commentair en'existe surement plus)
		this.InitHTMLLigne();

		var oChampDeb = eval("_PAGE_." + this.m_sAliasTable + "_DEB");
		var nDebut = 0;
		var sCleDebut;
		if (oChampDeb && !isNaN(parseInt(oChampDeb.value)))
		{
			nDebut = parseInt(oChampDeb.value) - 1;
			if ((oChampDeb.value + "").indexOf(";") > -1)
			{
				sCleDebut = (oChampDeb.value + "").substring((oChampDeb.value + "").indexOf(";") + 1);
			}
		}

		// Recupere le champ formulaire
		this.m_oFormulaire = eval("_PAGE_." + this.m_sAliasTable);

		// Recupere la selection (Uniquement en mode AWP)
		var sSelection;
		if (clWDAJAXMain.m_bPageAWP && this.m_oFormulaire)
		{
			sSelection = this.m_oFormulaire.value;

			// On cree un deuxieme champ a cote qui a le meme nom avec un _ en plus pour avoir la selection dans le format que l'on veux
			// (Indice en base 0 separe par des ;) sans perturbe le comportement des autres fonctions
			// On ajoute le champ a la page comme ceci il sera automatiquement place dans TOUS les submit (AJAX ou NON)
			var oFormulaireSelAWP = document.createElement("INPUT");
			oFormulaireSelAWP.type = "hidden";
			oFormulaireSelAWP.name = this.m_oFormulaire.name + "_SEL";
			oFormulaireSelAWP.id = this.m_oFormulaire.name + "_SEL";
			this.m_oFormulaireSelAWP = this.m_oFormulaire.parentNode.appendChild(oFormulaireSelAWP);
		}

		// Puis appele la fonction avec les donnees de reinitialisation
		this.Reinit(nColonneTrie, nDebut, sSelection, sCleDebut);
	},

	// Recupere le HTML d'une ligne dans la table
	InitHTMLLigne:function ()
	{
		this.m_sHTMLLigne = "";

		// Recupere la balise table.
		var oBaliseTable = this.oGetTableId(this.ID_TABLEINTERNE);

		// Et lit la valeur de la balise commentaire
		var i = 0;
		var nLimiteI = oBaliseTable.childNodes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			// Si c'est la balise commentaire
			if (oBaliseTable.childNodes[i].nodeName == "#comment")
			{
				this.m_sHTMLLigne += oBaliseTable.childNodes[i].nodeValue;
			}
			else if (oBaliseTable.childNodes[i].nodeName == "!")
			{	// Cas special pour IE5.5
				var sValeur = oBaliseTable.childNodes[i].text + "";
				// Supprime le <!-- initial et le --> final
				this.m_sHTMLLigne += sValeur.substr(4, sValeur.length - 7);
			}
		}

		// Remplace maintenant les commentaires
		this.m_sHTMLLigne = this.m_sHTMLLigne.replace(/\[%COMMENT%\]/g, "<!-- -->");
	},

	// Code commun a premiere init mais aussi au init suivantes
	Reinit:function (nColonneTrie, nDebut, sSelection, sCleDebut)
	{
		// Si trop d'init (ie trop d'erreurs) : on arrete
		if (++this.m_nNbInits > 16)
			return;

		// Affecte la colonne de tri
		if (!(nColonneTrie === undefined))
		{
			this.m_nColonneTrie = nColonneTrie;
			this.m_nColonneTriePre = nColonneTrie;
		}

		// Donnees dynamique qui doivent etre redefinie lors de la recreation de la table
		this.SetDebut(0);							// Premiere ligne affiche
		this.m_nNbLignes = 0;						// Nombre de lignes total de la table
		this.m_oRedimColonnes = new WDRedimColonnes(this);	// Le gestionnaire de redimensionnement des colonnes
		this.m_oGestionSaisie = new WDSaisieCellule(this);	// Gestion de la saisie
		this.m_tabSelection = new Array();			// Tableau des lignes selectionnees
		this.m_tabColonnes = new Array();			// Les proprietes des colonnes
		this.m_nNbLignesPage = this.m_bSansLimite ? 0 : -1;	// Nombre de ligne dans la page
		this.m_oCache = new WDTableCache(this);		// Notre cache

		// Restaure la selection si demande
		if (sSelection)
		{
			var nSelection = parseInt(sSelection);
			while (!isNaN(nSelection))
			{
				// Ajoute la ligne
				this.m_tabSelection.push(nSelection);

				// Trouve la valeur suivante
				if (sSelection.indexOf(this.SEL_SEPARATEUR) == -1)
					break;
				sSelection = sSelection.substring(sSelection.indexOf(this.SEL_SEPARATEUR) + 1);
				nSelection = parseInt(sSelection);
			}

			// Met la valeur dans le formulaire
			this.SetValFormulaire(isNaN(nSelection) ? "" : nSelection);
		}

		// Puis on initialise ce qui depend de la taille de la zone cliente :
		// Membre, defilement, HTML des lignes etc
		// En forcant la recuperation du chache
		this.OnResizeTable(true, false, nDebut, sCleDebut);

		this.AfficheMasque(true, false);
	},

	// Initialise la gestion de la roulette
	InitRoulette:function (oCible)
	{
		var oTmp = this;
		// Firefox
		if (oCible.addEventListener)
			oCible.addEventListener('DOMMouseScroll', function (event) { return oTmp.OnRoulette(event); }, false);
		else
			// IE
			oCible.onmousewheel = function () { return oTmp.OnRoulette(event); };
	},

	// Recoit le scrolling de la souris dans la zone cliente
	OnRoulette:function (oEvent)
	{
		var nDeplacement = 0;

		// IE
		if (oEvent.wheelDelta)
		{
			nDeplacement = -oEvent.wheelDelta / 180 * this.m_nHauteurLigne;
		}
		else
		{
			// Firefox
			nDeplacement = (oEvent.detail ? oEvent.detail : 0) * 20;
		}

		if (nDeplacement != 0)
			nDeplacement = this.nForceDefilement(nDeplacement);

		// S'il y a eu deplacement : Evite la remonte de l'evenement
		if (nDeplacement != 0)
		{
			if (oEvent.preventDefault)
				oEvent.preventDefault();
			oEvent.returnValue = false;
			return false;
		}
	},

	// Met a jour le nombre de lignes
	bMAJNbLignesVisibles:function (bMAJLignes, bTestUniquement)
	{
		// Calcule le nombre de ligne a afficher et met a jour ce nombre si besoin
		if (this.m_bSansLimite)
			return false;

		// Redimensionne l'ascenseur si besoin
		this.RedimAscenseur();

		// Et le nombre a changer
		var bNbLignesChange = this.bCalculeLigneAffiche(bTestUniquement);
		// Si c'est seulement un test : sort direct
		if (bTestUniquement) return bNbLignesChange;

		if (bNbLignesChange)
		{
			// Reaffiche les lignes
			this.GenereLignesHTML(this.m_nNbLignesPage + 2);
			this.InitLignesEtat(this.m_nNbLignesPage + 2);

			// Applique la visibilite des colonnes
			this.AfficheColonnes();

			// Force le recalcul des marges
			this.m_oCache.RecalculeMarges(this.m_nNbLignesPage, this.m_nPagesMargeMin, this.m_nPagesMargeMax, this.m_nPagesMargeRequete);
		}

		// Met a jour l'ascenseur de droite (Ajout d'une hauteur fixe pour l'ascenseur si besoin)
		this.MAJAscenseur();

		// Et met a jour l'affichage si besoin
		if (bMAJLignes && bNbLignesChange)
			this.MAJLignes();

		return bNbLignesChange;
	},

	// Recoit un redimensionnement de la table principale
	OnResizeTable:function (bForce, bMAJLignes, nDebut, sCleDebut)
	{
		// Met a jour le nombre de lignes
		this.bMAJNbLignesVisibles(bMAJLignes, false);

		// Remet les colonnes comme il faut si besoin
		if (!this.m_bDebordeLargeur)
			this.m_oRedimColonnes.ColonnesRestaure();

		// Si on a un nouveau debut a utiliser => Place la table a cet endroit
		// Force au passage le nombre de ligne de la table pour eviter que les verifs internes ne remettent le debut a zero
		// (La valeur provient du serveur donc a priori est valide)
		if (nDebut && (nDebut > 1))
		{
			this.SetDebut(nDebut, sCleDebut);
			this.m_nNbLignes = nDebut + this.m_nNbLignesPage;

			// Remet a jour le nombre de lignes
			this.RedimAscenseur();
			// Met a jour l'ascenseur de droite (Ajout d'une hauteur fixe pour l'ascenseur si besoin)
			this.MAJAscenseur(true);
		}

		// Et remplit finalement le cache si besoin => Cela va provoque une reception de donnee et un reaffichage
		this.m_oCache.RemplitCache(this.m_nDebut, bForce, -1, this.m_bSensTri);
	},

	// Initialise le tableau de l'etat des lignes
	InitLignesEtat:function (nNombre)
	{
		var i;
		var nLimiteI;

//		// Si besoin supprime l'ancien
//		if (this.m_tabLignesEtat)
//		{
//			// Commence par supprimer les objets du tableau : utile ?????
//			nLimiteI = this.m_tabLignesEtat.length;
//			for (i = 0; i < nLimiteI; i++)
//			{
//				delete this.m_tabLignesEtat[i].m_bVisible;		// Par defaut ligne invisible
//				delete this.m_tabLignesEtat[i].m_bPlein;		// Et ligne non remplie
//				delete this.m_tabLignesEtat[i].m_oLigne;
//				this.m_tabLignesEtat[i] = null;
//			}
//			// Et supprime le tableau
//			delete this.m_tabLignesEtat;
//		}

		this.m_tabLignesEtat = new Array(nNombre);
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.m_tabLignesEtat[i] = new Object();
			this.m_tabLignesEtat[i].m_bVisible = false;		// Par defaut ligne invisible
			this.m_tabLignesEtat[i].m_bPlein = false;		// Et ligne non remplie
			this.m_tabLignesEtat[i].m_oLigne = this.oGetTableId(i);
		}
	},

	// Recalcule le nombre de ligne a afficher dans la page et change le nombre de lignes si besoin
	// Renvoi vrai si le nombre de ligne a ete (Ou va si bTestUniquement est a vrai) changer
	bCalculeLigneAffiche:function (bTestUniquement)
	{
		//assert(!this.m_bSansLimite);

		// Calcule le nouveau nombre de lignes
		var oBaliseTable = this.oGetTableId(this.ID_TABLEINTERNE);
		var nHauteur = parseInt(bIE ? oBaliseTable.height : oBaliseTable.getAttribute("height"));
		if (isNaN(nHauteur) || (nHauteur == 0)) nHauteur = parseInt(_JGCS(oBaliseTable).height);
		if (isNaN(nHauteur) || (nHauteur == 0)) nHauteur = parseInt(oBaliseTable.style.height);
		if (isNaN(nHauteur) || (nHauteur < this.m_nHauteurLigne)) nHauteur = this.m_nHauteurLigne;

		// Decalage a cause du debordement en largeur
		if (this.m_bDebordeLargeur) nHauteur -= 18;

		var nNbLignesPage = Math.floor(nHauteur / this.m_nHauteurLigne);

		// Si le nombre de ligne change
		if (nNbLignesPage != this.m_nNbLignesPage)
		{
			// Si ce n'est pas seulement un test : met a jour la valeur
			if (!bTestUniquement)
				this.m_nNbLignesPage = nNbLignesPage;
			return true;
		}
		return false;
	},

	// Defini la fonction navigateur a appeler lors de la selection d'un ligne de table
	CodeSelection:function (sCodeAppel)
	{
		this.m_fPCodeSelectionNav = new Function("event", "return " + sCodeAppel + ";");
	},

	// Rafraichi la table
	Refresh:function (nReset, nNouveauDebut, sCleNouveauDebut)
	{
		// Indique s'il faut MAJ l'ascenseur
		var bMajAscenseur = false;
		// Si reaffichage de la table
		switch (nReset)
		{
		case 1:
			this.SetDebut(0);
			// Il n'y a pas d'ascenseur en mode sans limite
			if (!this.m_bSansLimite)
				bMajAscenseur = true;
			// Pas de break ???
		case 2:
			// Se repositionne si besoin
			if (nNouveauDebut > -1)
			{
				this.SetDebut(nNouveauDebut, sCleNouveauDebut);
				// On doit remettre l'ascenseur
				bMajAscenseur = true;
			}
			break;
		case 0:
		default:
			break;
		}

		// Si on doit remettre l'ascenseur et que l'on a bien un ascenseur
		if (bMajAscenseur && !this.m_bSansLimite)
			this.MAJAscenseur(true);

		// Vire le cache
		this.m_oCache.RemplitCache(this.m_nDebut, true, this.m_nColonneTrie, this.m_bSensTri);

		// Marque toutes les lignes comme invalide
		var i = 0;
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.m_tabLignesEtat[i].m_bPlein = false;
		}
		// Et affiche le masque qui empeche la saisie
		this.AfficheMasque(true, true);
	},

	// Defini le debut de la table
	SetDebut: function (nNouveauDebut, sCleNouveauDebut)
	{
		this.m_nDebut = nNouveauDebut;
		if (sCleNouveauDebut)
			this.m_sCleDebut = sCleNouveauDebut;
		else
			delete this.m_sCleDebut;
	},

	// Envoie une requete de selection
	RequeteSelection:function (nColonneLien)
	{
		// Sauve la valeur du formulaire
		var sFormulaire = this.m_oFormulaire ? this.m_oFormulaire.value : "";
		// Et met la valeur complete
		this.SetValFormulaire(this.sConstruitRequeteSelection());

		// Creer une requete de modification de ligne
		this.m_oCache.CreeRequeteSelection(nColonneLien);

		// Restaure le formulaire
		this.SetValFormulaire(sFormulaire);

		// Et affiche le masque qui empeche la saisie
		this.AfficheMasque(true, true);
	},

	// Construit l'id d'un element de la table
	sGetTableId:function (sNomElement, sSousElement1, sSousElement2)
	{
		return this.m_sAliasTable + this.ID_SEPARATEUR + sNomElement + ((sSousElement1 === undefined) ? "" : ((this.ID_SEPARATEUR + sSousElement1) + ((sSousElement2 === undefined) ? "" : (this.ID_SEPARATEUR + sSousElement2))));
	},

	// Recupere un element de la table en construisant son identifiant
	oGetTableId:function (sNomElement, sSousElement1, sSousElement2)
	{
		return oGetId(this.sGetTableId(sNomElement, sSousElement1, sSousElement2));
	},

	// Cree un timeout sur la table et renvoi l'id du timeout
	nSetTimeoutTable:function (sFonction, nDuree, sParam)
	{
		return setTimeout(this.m_sNomVariable + "." + sFonction + "(" + (sParam ? sParam : "") + ");", nDuree);
	},

	// On stocke la derniere ligne selectionnee dans le formulaire
	SetValFormulaire:function (sValeur)
	{
		// Et ecrit dedans s'il est disponible
		if (this.m_oFormulaire) this.m_oFormulaire.value = sValeur;

		// Et ecrit dans le champ cache spacial AWP si il est disponible
		if (this.m_oFormulaireSelAWP)
			this.m_oFormulaireSelAWP.value = this.sConstruitRequeteSelection();
	},

	// Indique si une ligne est selectionnee : renvoie son indice dans le tableau des selection ou -1 sinon
	nLigneSelectionne:function (nLigneAbsolue)
	{
		var tabSelection = this.m_tabSelection;
		// Recherche dans le tableau
		var i;
		var nLimiteI = tabSelection.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if (tabSelection[i] == nLigneAbsolue)
				return i;
		}

		// Pas trouve
		return -1;
	},

	// Selectionne une ligne
	// Indique si la ligne a ete selectionnee (Faux si la ligne etait deja selectionnee)
	bLigneSelectionne:function (nLigneAbsolue, bCodeNav, oEvent)
	{
		// Seulement si la ligne n'existe pas encore
		if (this.nLigneSelectionne(nLigneAbsolue) != -1) return false;

		// Ajoute la ligne
		this.m_tabSelection.push(nLigneAbsolue);
		// Si la ligne est visible : lui change son style
		if (((this.m_nNbLignesPage == 0) && (nLigneAbsolue < this.m_nNbLignes)) || ((this.m_nNbLignesPage != 0) && (nLigneAbsolue >= this.m_nDebut) && (nLigneAbsolue <= this.m_nDebut + this.m_nNbLignesPage + 1)))
		{
			this.m_tabLignesEtat[nLigneAbsolue - this.m_nDebut].m_oLigne.className = this.m_tabStyle[2];
			// Et force sa MAJ
			this.MAJLigne(nLigneAbsolue);
		}

		// Et on stocke dans le membre de la page la derniere ligne selectionnee
		this.SetValFormulaire(nLigneAbsolue + 1);

		// Appel le PCode navigateur de selectionne de ligne
		if (this.m_fPCodeSelectionNav && bCodeNav)
			this.m_fPCodeSelectionNav(oEvent);

		return true;
	},

	// Deselectionne une ligne
	// Indique si la ligne a ete selectionnee (Faux si la ligne etait deja selectionnee)
	bLigneDeselectionne:function (nIndiceTableau)
	{
		var tabSelection = this.m_tabSelection;

		// Si la ligne n'est pas dans le tableau
		if ((nIndiceTableau < 0) || (nIndiceTableau >= tabSelection.length))
			return false;

		// Recupere le numero de la ligne
		var nLigneAbsolue = tabSelection[nIndiceTableau];

		// Et on vire la ligne du tableau
		tabSelection.splice(nIndiceTableau, 1);
		
		// Si la ligne est visible : lui change son style
		if (((this.m_nNbLignesPage == 0) && (nLigneAbsolue < this.m_nNbLignes)) || ((this.m_nNbLignesPage != 0) && (nLigneAbsolue >= this.m_nDebut) && (nLigneAbsolue <= this.m_nDebut + this.m_nNbLignesPage + 1)))
		{
			this.m_tabLignesEtat[nLigneAbsolue - this.m_nDebut].m_oLigne.className = (nLigneAbsolue % 2) == 0 ? this.m_tabStyle[0] : this.m_tabStyle[1];
			// Et force sa MAJ
			this.MAJLigne(nLigneAbsolue);
		}

		// On met a jour l'eventuel element du formulaire
		this.SetValFormulaire(tabSelection.length > 0 ? (tabSelection[tabSelection.length - 1] + 1) : "");

		// Supprimee
		return true;
	},

	// Deselectionne tous
	bLigneDeselectionneTous:function (nLigneGarde)
	{
		var bChangement = false;
		var tabSelection = this.m_tabSelection;
		var nPos = 0;

		while (tabSelection.length > nPos)
		{
			// Uniquement si ce n'est pas la ligne filtree
			if (nLigneGarde != tabSelection[nPos])
			{
				bChangement = this.bLigneDeselectionne(nPos);
			}
			else
			{
				nPos++;
			}
		}
		return bChangement;
	},

	// Calcule la partie de selection transmise dans la requete
	sConstruitRequeteSelection:function ()
	{
		return this.m_tabSelection.join(this.SEL_SEPARATEUR);
	},

	// Traite la reponse du serveur : remplissage du cache et affichage des donnees si besoin
	bActionListe:function (oXMLLignes)
	{
		// Sauve l'ancien nombre de lignes
		var nAncienNbLignes = this.m_nNbLignes;
		// Relit le nombre de ligne total de la table (Cas d'une table fichier)
		this.m_nNbLignes = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_NOMBRE));
		// Puis le place dans l'element du formulaire pour une utilisation en JS (TB53093)
		var oChampOcc = eval("_PAGE_._" + this.m_sAliasTable + "_OCC");
		if (oChampOcc)
		{
			oChampOcc.value = this.m_nNbLignes;
		}

		// Regarde si on doit ne pas notifier le serveur des selections
		if (clWDAJAXMain.bXMLAttributExiste(oXMLLignes, this.XML_DESACTIVESERSEL))
			this.m_bRetourServeurSelection = false;

		// Enregsitre si c'est une table ou une ZR
		if (clWDAJAXMain.bXMLAttributExiste(oXMLLignes, WDAJAXMain.prototype.XML_CHAMP_ATT_TYPE))
			this.m_nType = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, WDAJAXMain.prototype.XML_CHAMP_ATT_TYPE));

		// Si on affiche toutes les lignes : cree le HTML et redimensionne les tableaux d'etat
		if (this.m_bSansLimite)
		{
			// On ne le fait que si le nombre de ligne a change ou dans les tables (Inutile ?)
			// Dans les ZR cela gene la saisie en cascade
			if ((nAncienNbLignes != this.m_nNbLignes) || (this.m_nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_TABLE))
			{
				this.GenereLignesHTML(this.m_nNbLignes);
				this.InitLignesEtat(this.m_nNbLignes);

				// Applique la visibilite des colonnes
				this.AfficheColonnes();
			}
			else if (this.m_nNbLignes == 0)
			{
				// Si on a pas de ligne dans une ZR sans limite : on doit quand meme creer le tableau de m_tabLignesEtat
				this.InitLignesEtat(0);
			}
		}

		// Si on a l'attribut du numero de la colonne tri : actualise l'affichage
		if (clWDAJAXMain.bXMLAttributExiste(oXMLLignes, this.XML_TRI))
		{
			this.m_nColonneTriePre = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_TRI));
			this.m_bSensTriPre = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_SENS)) != 0;

			// Actualise l'affichage des pictos
			this.AfficheTriColonne(this.m_nColonneTriePre, !this.m_bSensTriPre);
		}
		else
		{
			delete this.m_nColonneTriePre;
			delete this.m_bSensTriPre;
		}

		// .. Si on n'a pas le nombre total de ligne : note d'envoyer une requete pour faire le parcours en arriere plan
		this.m_bFinTrouve = parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_FIN)) == 1;
		this.m_sCleParcourtAP = clWDAJAXMain.sXMLGetAttributSafe(oXMLLignes, this.XML_CLEENREGAP);

		// Si on recoit une commande de reset : l'execute
		switch (clWDAJAXMain.sXMLGetAttributSafe(oXMLLignes, WDAJAXMain.prototype.XML_CHAMP_REFRESH_RESETTABLE))
		{
		case "1":
			// Si on a l'ttribut de position : se place au bon endroit (Utiliser par la recherche
			if (clWDAJAXMain.bXMLAttributExiste(oXMLLignes, this.XML_DEBUT))
				this.Refresh(1, parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_DEBUT)));
			else
				this.Refresh(1);
			return false;
		case "2":
			this.Refresh(2);
			return false;
		default:
			// Si on a l'ttribut de position : se place au bon endroit (Utiliser par la recherche
			if (clWDAJAXMain.bXMLAttributExiste(oXMLLignes, this.XML_DEBUT))
			{
				this.SetDebut(parseInt(clWDAJAXMain.sXMLGetAttribut(oXMLLignes, this.XML_DEBUT)));
			}
			break;
		}

		// Vide le cache des donnees inutiles
		this.m_oCache.SupprimeLignesInutiles();

		// On lance le parcours des nodes filles
		var oXMLFils = oXMLLignes.firstChild;
		while(oXMLFils != null)
		{
			// Selon le type d'action
			switch(oXMLFils.nodeName)
			{
				// Remplit le cache avec les donnees recues
				case this.XML_LIGNES:		this.m_oCache.RemplitCacheLignes(oXMLFils);	break;
				// Selection
				case this.XML_SELECTIONS:	this.RemplitSelection(oXMLFils);			break;
				// Remplit les proprietes des colonnes
				case this.XML_COLONNES:		this.RemplitColonnes(oXMLFils);				break;
				// Affiche une erreur
				case this.XML_ERREUR:		this.AfficheErreur(oXMLFils);				break;
			}
			// On passe au fils suivant
			oXMLFils = oXMLFils.nextSibling;
		}

		// Si on a un ascenseur
		if (!this.m_bSansLimite)
		{
			// Met a jour la bare de defilement et les lignes visible a l'ecran
			this.MAJAscenseur(clWDAJAXMain.bXMLAttributExiste(oXMLLignes, this.XML_DEBUT));
		}

		// Et met a jour l'affichage
		this.MAJLignes();

		// Essaie de chercher la fin si besoin
		if (this.m_bFinTrouve == false)
			this.ChercheFin();

		// Decide si il faut afficher ou non le picto de travail
		this.AfficheLoading();

		return true;
	},

//	// Retourne la bulle d'une colonne
//	sGetBulle:function (sColonne)
//	{
//		var sBulle = this.m_tabColonnesBulles[sColonne];
//		return sBulle ? sBulle : "";
//	},

	// Genere le nombre de ligne HTML qui va bien pour afficher le tableau
	GenereLignesHTML:function (nNbLignes)
	{
		// Recupere la balise table.
		var oBaliseTable = this.oGetTableId(this.ID_TABLEINTERNE);

		// Supprime les lignes actuelles
		while (oBaliseTable.childNodes.length)
			oBaliseTable.removeChild(oBaliseTable.childNodes[0]);

		// Cree des nouvelles lignes
		var i = 0;
		var nLimiteI = this.m_bSansLimite ? this.m_nNbLignes : this.m_nNbLignesPage + 2;
		
		// Récupère le HTML des lignes
		var sHTMLLigne = this.m_sHTMLLigne;
		// Remplace le mot clé de l'ancien mode par celui du nouveau mode
		var rRemplacement = new RegExp("\\[%" + this.m_sAliasTable + "%\\]", "g");
		sHTMLLigne = sHTMLLigne.replace(rRemplacement, "[%_INDICE_%]");

		// Le tableau du HTML des lignes
		var tabHTMLLigne = new Array(nLimiteI);

		for (i = 0; i < nLimiteI; i++)
		{
			// Construit le HTML de la nouvelle ligne
			// Remplacer les balises indiquant le numéro de ligne
			tabHTMLLigne[i] = sHTMLLigne.replace(/\[%_INDICE_%\]/g, i);
//			// Plus de remplacement des commentaires (deja fait au chargement du HTML)
//			var oTmp = this;
//			tabHTMLLigne[i] = tabHTMLLigne[i].replace(/\[%(\w+)\.\.BULLE%\]/g, function(sVal, sCol) { return oTmp.sGetBulle(sCol) } );
		}

		// Et selon le navigateur on recree la table differement
		if (bIE)
		{
			// Pour IE

			// Recupere le HTML
			var sHTMLTable = oBaliseTable.outerHTML;
			sHTMLTable = clWDAJAXMain.sSansEspace(sHTMLTable, true, true, true);

			// Et contruit le HTML global
			oBaliseTable.outerHTML = sHTMLTable.substr(0, sHTMLTable.length - "</TABLE>".length) + tabHTMLLigne.join("") + "</TABLE>";
		}
		else
		{
			var oRange = document.createRange();
			oRange.setStart(oBaliseTable, 0);
			oBaliseTable.appendChild(oRange.createContextualFragment(tabHTMLLigne.join("")));
			// Force le reflow de la page car sinon firefox ne le fait pas bien (Au moins jusqu'a la version
			oBaliseTable.style.width = oBaliseTable.style.width;
		}
	},

	// Remplit la table des lignes selectionnees
	RemplitSelection:function (oXMLSelections)
	{
		// Supprime la selection memorise courante
		this.bLigneDeselectionneTous(-1);

		// Met a jour le cache des selections
		var oXMLSelection = oXMLSelections.firstChild;
		while (oXMLSelection != null)
		{
			// Lit le numero de la ligne
			//assert(XMLLigne.nodeName == this.XML_SELECTION);
			var nSelection = parseInt(clWDAJAXMain.sXMLGetValeur(oXMLSelection));
			this.bLigneSelectionne(nSelection, false);

			// Passe a la selection suivante
			oXMLSelection = oXMLSelection.nextSibling;
		}
	},

	// Remplit les proprietes des colonnes
	RemplitColonnes:function (oXMLColonnes)
	{
		// Vide la liste des colonnes
		this.m_tabColonnes = new Array(oXMLColonnes.childNodes.length)
		var tabColonnes = this.m_tabColonnes;

		// Puis parse le XML
		var oXMLColonne = oXMLColonnes.firstChild;
		var nPos = 0;
		while (oXMLColonne != null)
		{
			// Lit le numero de la ligne
			//assert(XMLLigne.nodeName == this.XML_COLONNE);
			tabColonnes[nPos++] = new WDColonne(oXMLColonne);
			// Passe a la selection suivante
			oXMLColonne = oXMLColonne.nextSibling;
		}

		// Applique la visibilite des colonnes
		this.AfficheColonnes();
	},

	// Applique la visibilite des colonnes
	AfficheColonnes:function()
	{
		var tabColonnes = this.m_tabColonnes;
		var i;
		var nLimiteI = tabColonnes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.AfficheColonne(i, tabColonnes[i].m_bVisible ? (bIE ? "block" : "table-cell") : "none");
		}
	},

	// Effectue les modifications de visibilite requise sur les colonnes
	AfficheColonne:function (nColonne, sDisplay)
	{
		// Affiche ou pas le titre de la colonne
		this.AfficheCellule(this.ID_TITRE, nColonne, 2, sDisplay);

		// Si la colonne n'existe pas (Pas de ligne visible) alors on ne fait rien
		var oPremiereCellule = this.oGetTableId(0, nColonne);
		if (!oPremiereCellule)
			return;

		// Defini la taille de la colonne cote donnees
		// Si le style est deje OK ne fait rien
		var oDiv = oPremiereCellule.parentNode.parentNode;
		var oCelluleCurrentStyle = _JGCS(oDiv);
		if (oCelluleCurrentStyle.display == sDisplay)
			return;

		// Applique le style sur les lignes
		var i = 0;
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.AfficheCellule(i, nColonne, 2, sDisplay);
		}
	},

	// Effectue les modifications de visibilite requise sur une cellule
	AfficheCellule:function (nLigne, nColonne, nNiveau, sDisplay)
	{
		// Recupere le DIV
		var oDiv = this.oGetTableId(nLigne, nColonne);
		if (!oDiv) return;
		while (nNiveau-- > 0)
		{
			// On un niveau de DIV de plus
			oDiv = oDiv.parentNode;
			if (!oDiv) return;
		}
		// On est sur le TD parent
		oDiv.style.display = sDisplay;
		// Dans FireFox : oDiv.nextSibling ne suffit pas car on trouve un noeud texte entre les balises
		var oTDSuivant = oDiv.nextSibling;
		while (oTDSuivant && oTDSuivant.nodeName == "#text")
		{
			oTDSuivant = oTDSuivant.nextSibling;
		}
		// Il faut aussi rendre invisible la colonne de redimensionnement
		if (oTDSuivant && (oTDSuivant.id.length == 0))
		{
			oTDSuivant.style.display = sDisplay;
		}
	},

	// Affiche un erreur sur les tables
	AfficheErreur:function (oXMLErreur)
	{
		// Recupere le texte de l'erreur
		alert(clWDAJAXMain.sXMLGetValeur(oXMLErreur));
	},

	// Remplit une cellule de la table/ZR
	RemplitCellule:function (oCellule, sValeur, nLigneAbsolue, nColonne)
	{
		// Selon si on est dans une table ou une ZR
		switch (this.m_nType)
		{
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_ZONEREPETEE:	// ZR
			this.RemplitCelluleZR(oCellule, sValeur, nLigneAbsolue, nColonne);
			break;
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_TABLE:			// Table
		default:												// Et autre aussi
			this.RemplitCelluleTable(oCellule, sValeur, nLigneAbsolue, nColonne);
			break;
		}
	},

	// Remplit une cellule de la ZR
	RemplitCelluleZR:function (oCellule, sValeur, nLigneAbsolue, nColonne)
	{
		// Vide la cellule
		var tabChildNodes = oCellule.childNodes;
		while (tabChildNodes.length > 0)
			oCellule.removeChild(tabChildNodes[0]);

		// Et place le contenu dans la cellule
		oCellule.innerHTML = clWDEncode.sEncodeInnerHTML(sValeur, false, true);

		var oTmp = this;
		var sCelluleId = oCellule.id;
		var fChange = function () { oTmp.OnValideLigneZR(sCelluleId, nLigneAbsolue, true); };

		// Puis on hook le onblur de tous les champs de la selection
		var tabElements = this.tabGetElements(oCellule);
		var i;
		var nLimiteI = tabElements.length;
		for (i = 0; i < nLimiteI; i++)
		{
			// @@@ Gestion des anciennes fonctions ???
			var oElement = tabElements[i];
			if (oElement.onchange === undefined)
			{
				this.SetOnBlur(oElement, sCelluleId, nLigneAbsolue, true);
			}
			else
			{
				this.SetOnBlur(oElement, sCelluleId, nLigneAbsolue, false);
				// Ne hook que le onchange que s'il n'existe pas
				if (!oElement.onchange)
				{
					oElement.onchange = fChange;
				}
			}

			// On doit passer par une fonction pour les valeurs des variables sinon on a la valeurs de i lors de sa destruction
			this.SetOnFocus(oElement, sCelluleId, nLigneAbsolue, i);
		}

		// Fait de meme pour les elements A de la ligne
		tabElements = oCellule.getElementsByTagName("A");
		nLimiteI = tabElements.length;
		for (i = 0; i < nLimiteI; i++)
		{
			// On doit passer par une fonction pour les valeurs des variables sinon on a la valeurs de i lors de sa destruction
			this.SetOnFocus(tabElements[i], sCelluleId, nLigneAbsolue, i);
		}

	},

	// On doit passer par une fonction pour les valeurs des variables
	SetOnBlur:function (oElement, sCelluleId, nLigneAbsolue, bFlag)
	{
		var oTmp = this;
		var pfnOldBlur = oElement.onblur;
		if (bIE)
			oElement.onblur = function () { if (pfnOldBlur) pfnOldBlur.apply(oElement); oTmp.OnValideLigneZR(sCelluleId, nLigneAbsolue, bFlag); };
		else
			oElement.onblur = function (event) { if (pfnOldBlur) pfnOldBlur.apply(oElement, [event]); oTmp.OnValideLigneZR(sCelluleId, nLigneAbsolue, bFlag); };
	},

	// On doit passer par une fonction pour les valeurs des variables sinon on a la valeurs de i lors de sa destruction
	SetOnFocus:function (oElement, sCelluleId, nLigneAbsolue, i)
	{
		var oTmp = this;
		var pfnOldFocus = oElement.onfocus;
		if (bIE)
			oElement.onfocus = function () { if (pfnOldFocus) pfnOldFocus.apply(oElement); oTmp.OnFocusLigneZR(sCelluleId, nLigneAbsolue, i); };
		else
			oElement.onfocus = function (event) { if (pfnOldFocus) pfnOldFocus.apply(oElement, [event]); oTmp.OnFocusLigneZR(sCelluleId, nLigneAbsolue, i); };
	},

	// Redimensionne une image en gardant son aspect
	RedimImage: function (oImage)
	{
		if (bIE && (oImage.readyState != "complete"))
			return;

		// Recupere les dimensions
		var nImageX = oImage.offsetWidth;
		var nImageY = oImage.offsetHeight;
		var nDispoX = oImage.parentNode.parentNode.clientWidth;
		var nDispoY = oImage.parentNode.parentNode.clientHeight;

		var nFinalX = nDispoX;
		var nFinalY = nDispoY;

		// Si une des dimensions est trop petite
		if (nImageX <= nDispoX)
		{
			if (nImageY <= nDispoY)
			{
				// L'image est plus petite que la place disponible
				// Ne fait rien => Le navigateur affiche deje bien l'image
				return;
			}
			else
			{
				// La dimension verticale est la contrainte
				nFinalX = nImageX * nDispoY / nImageY;
//				nFinalY = nDispoY;
			}
		}
		else if (nImageY <= nDispoY)
		{
			// La dimension Y est trop petite
			// Pas besoin de tester le cas X trop petit (Deje teste) => la dimension horizontale est la contrainte
//			nFinalX = nDispoX;
			nFinalY = nImageY * nDispoX / nImageX;
		}
		else
		{
			// L'image est trop grande dans les deux dimensions

			// Calcule les deux ratios de dimension
			var dRImage = nImageX / nImageY;
			var dRDispo = nDispoX / nDispoY;
			// On se place dans une ou l'autre dimension
			if (dRDispo > dRImage)
			{	// La contrainte est la hauteur
				nFinalX = nDispoY * dRImage;
//				nFinalY = nDispoY;
			}
			else
			{	// La contrainte est la largeur
//				nFinalX = nDispoX;
				nFinalY = nDispoX * dRImage;
			}
		}
		oImage.style.width = nFinalX + "px";
		oImage.style.height = nFinalY + "px";
	},

	// Gestion de la modification des interrupteurs
	ClickInterrupteur:function (oEvent, oInterrupteur, nLigneAbsolue, nColonne)
	{
		var nLigneRelative = nLigneAbsolue - this.m_nDebut;

		var sValeur = oInterrupteur.checked ? "1" : "0";
		var nValeur = oInterrupteur.checked ? 1 : 0;

		// Cree la ligne virtuelle si besoin
		this.CreeLigneVirtuelle(nLigneRelative);
		// Valide le changement
		this.ChangementValeur(nLigneRelative, nColonne, sValeur, nValeur);
		// Et notifie le serveur
		this.ValideChangement(nLigneRelative);
		// Et on demande la MAJ de la ligne
		this.bMAJLigne(nLigneRelative);
		// Supprime la ligne virtuelle
		this.SupprimeLignesVirtuelles();
	},

	// Remplit une cellule de la table
	RemplitCelluleTable:function (oCellule, sValeur, nLigneAbsolue, nColonne)
	{
		// L'element cree
		var oElement;
		var oTmp = this;

		// Place la bulle de la colonne
		if (this.m_tabColonnes[nColonne].m_sBulle)
		{
			oCellule.title = this.m_tabColonnes[nColonne].m_sBulle;
		}
		else
		{
			oCellule.removeAttribute("TITLE", 0);
		}

		// Selon le type de la colonne
		switch (this.nColonneType(nColonne))
		{
		// Interrupteur
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR:
			// Creation d'un objat INPUT avec type="checkbox"
			oElement = document.createElement("INPUT");
			oElement.type = "checkbox";
			// Coche ?
			oElement.checked = (sValeur == "1");
			// Calcul du nom et affectation a NAME et a ID
			var sName = "_" + nLigneAbsolue + "_" + this.m_sAliasTable + "_" + nColonne
			oElement.name = sName;
			oElement.id = sName;
			// Si saissisable
			if (this.bColonneSaisissable(nColonne))
			{
				if (bIE)
				{
					oElement.onclick = function () { oTmp.ClickInterrupteur(event, oElement, nLigneAbsolue, nColonne); event.returnValue = false; return false;};
				}
				else
				{
					oElement.onclick = function (event) { oTmp.ClickInterrupteur(event, oElement, nLigneAbsolue, nColonne); if (event.preventDefault) event.preventDefault(); return false;};
				}
			}
			else
			{
				oElement.disabled = true;
			}

			// Centre l'interrupteur dans le parent
			oCellule.style.textAlign = "center";
			oCellule.style.verticalAlign = "middle";
			break;

		// Colonne image
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE:
			// Creer un champ image avec la bonne source
			if (sValeur.length > 0)
			{
				oElement = document.createElement("IMG");
				oElement.src = sValeur;
				if (bIE)
					oElement.onreadystatechange = function () { oTmp.RedimImage(oElement); };
				else
					oElement.onload = function () { oTmp.RedimImage(oElement); };

				// Si la colonne est lien actif
				if ((this.nColonneLien(nColonne) > 0) && ((this.nColonneEtatLien(nColonne) == 0) || (this.nColonneEtatLien(nColonne) == 5)))
				{
					if (bIE)
						oElement.onclick = function () { oTmp.OnColonneLien(nLigneAbsolue, nColonne, event); };
					else
						oElement.onclick = function (event) { oTmp.OnColonneLien(nLigneAbsolue, nColonne, event); };
				}

				// Centre l'image dans le parent
				oCellule.style.textAlign = "center";
				oCellule.style.verticalAlign = "middle";
			}
			break;

		// Colonne combo (liste de valeurs)
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO:
			// Pas de brak; car comme colonne en saisie pour ce qui est de l'affichage => Simple texte

		// Colonne "simple" : colonne en saisie
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE:
		default:
			// Si la colonne n'est pas lien : remplit simplement la cellule
			switch (this.nColonneLien(nColonne))
			{
			default:
			case 0:
				// Si on est en encodage latin-1 (Donc pas en UTF-8) : On encode les caracteres > 127
				// Pas besoin de le faire en UTF-8 car il on deja ete encode pour avoir au final la bonne valeur unicode
				oElement = document.createTextNode(clWDEncode.sEncodeCharset(sValeur, false));
				break;
			case 1:	// Lien
			case 2:	// Lien avec submit
				// Sinon on rajoute un lien autour
				oElement = document.createElement("A");
				oElement.href = "javascript:void(0);"

				// Calcule l'etat du lien
				switch (this.nColonneEtatLien(nColonne))
				{
				default:
				case 0:	// Actif
				case 5:	// AffSansSel
					var oTmp = this;
					if (bIE)
						oElement.onclick = function () { oTmp.OnColonneLien(nLigneAbsolue, nColonne, event); };
					else
						oElement.onclick = function (event) { oTmp.OnColonneLien(nLigneAbsolue, nColonne, event); };
					break;
				case 4:	// Grise
					oElement.disabled = true;
					// Pas de 'break;' : grise => DISABLED + READONLY
				case 1:	// Lecture seule
					oElement.readOnly = true;
					break;
				}

				oElement.innerHTML = clWDEncode.sEncodeInnerHTML(sValeur, true);
				break;
			}
			break;
		}

		// Supprime le contenu actuel
		while (oCellule.childNodes.length)
			oCellule.removeChild(oCellule.childNodes[0]);

		var oNewElement;
		if (oElement)
		{
			// Et ajoute l'element. On perd la selection de la case a cocher (????)
			var oChecked = oElement.checked;
			oNewElement = oCellule.appendChild(oElement);
			// Fait un simple test pour la restauration car de toute facon soit c'est undefined, a false ou a true
			// Comme la valeur par defaut est false on n'a pas de probleme
			if (oChecked) oElement.checked = true;
		}

		if (this.nColonneType(nColonne) == WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE)
		{
			if (oNewElement) this.RedimImage(oNewElement);
		}

		// Si la colonne n'est pas saisissable : affiche un autre curseur
		if (this.bColonneSaisissable(nColonne) == false)
		{
			oCellule.style.cursor = "default";
		}
	},

	// Decide si il faut afficher ou non le picto de travail
	AfficheLoading:function ()
	{
		var oImgChargement = this.oGetTableId(this.ID_CHARGEMENT_IMG);
		if (oImgChargement)
		{
			oImgChargement.style.visibility = ((!this.m_bFinTrouve) || (this.m_oCache.m_tabRequetes.length > 0)) ? "inherit" : "hidden";
		}
	},

	// Envoie une requete (Via un timer) pour avoir la fin du fichier apres une petite temporisation
	// Verifie si on n'a pas une requete normale en cours pour ne pas lancer la requete de remplissage
	// On ne peu etre e la fin car on remplit en arriere plan
	ChercheFin:function ()
	{
		// Si on a deja une requete en cours => Ne fait rien
		// Ou si on connait la fin
		if ((this.m_oCache.m_tabRequetes.length > 0) || this.m_bFinTrouve)
		{
			return;
		}

		// Note d'envoier une requete aux serveur dans ce domaine dans une seconde
		this.nSetTimeoutTable("EnvoieChercheFin", 1000);
	},

	// Envoie un requte pour calculer la fin
	EnvoieChercheFin:function ()
	{
		// Si on a deja une requete en cours => Ne fait rien
		// Ou si on connait la fin (Requete arrive entre temps)
		if ((this.m_oCache.m_tabRequetes.length > 0) || this.m_bFinTrouve)
		{
			return;
		}

		// Cree une requete que l'on envoie
//		this.m_oCache.CreeRequete(new Segment(-1, -1), -1, true, this.m_nCleEnregPosParcourAP, this.m_sCleEnregParcourAP)
		this.m_oCache.CreeRequete(new Segment(-1, -1), -1, true, -1, "")
	},

	// Rafraichit les lignes visible si besoin
	MAJLignes:function ()
	{
		// Parcours les lignes invalides
		var i = 0;
		var nNumPlein = 0;
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			var oLigneEtat = this.m_tabLignesEtat[i];

			// Si la table ne peux etre completement affichee et que l'on est dans une ligne masque
			if ((this.m_nDebut == 0) && (this.m_nNbLignes < this.m_nNbLignesPage) && (i >= this.m_nNbLignes))
			{
				// Si la ligne est visible => La masque
				if (oLigneEtat.m_bVisible)
				{
					this.AfficheLigne(oLigneEtat, false);
					// Et la note comme etant vide
					oLigneEtat.m_bPlein = false;
				}
				// Mais c'est un cas ou il faut compter la ligne comme pleine
				nNumPlein++;
				// On ne fait rien de plus
				continue;
			}
			// Si on est en bas et que l'on a la derniere ligne (Un des deux en plus => Il ne faut pas l'afficher
//			else if ((i >= (nLimiteI - 2)) && (this.m_nDebut + this.m_nNbLignesPage >= this.m_nNbLignes))
			else if (this.m_nDebut + i >= this.m_nNbLignes)
			{
				// Si la ligne est visible => La masque
				if (oLigneEtat.m_bVisible)
				{
					this.AfficheLigne(oLigneEtat, false);
					// Et la note comme etant vide
					oLigneEtat.m_bPlein = false;
				}
				// Mais c'est un cas ou il faut compter la ligne comme pleine
				nNumPlein++;
				// On ne fait rien de plus
				continue;
			}
			else if (!oLigneEtat.m_bVisible)
			{	// L'affiche dans le cas inverse
				this.AfficheLigne(oLigneEtat, true);
			}

			// Met le style de la ligne
			// Ligne "impaire" (La numerotation commence a zero pour la ligne 1)
			var sStyle = ((this.m_nDebut + i) % 2) == 0 ? this.m_tabStyle[0] : this.m_tabStyle[1];
			// Si la ligne est selectionne, on met un style particulier s'il y en a un
			if ((this.nLigneSelectionne(this.m_nDebut + i) != -1) && (this.m_tabStyle[2].length > 0))
			{
				sStyle = this.m_tabStyle[2];
			}

			if (oLigneEtat.m_oLigne.className != sStyle)
			{
				oLigneEtat.m_oLigne.className = sStyle;
			}

			if (!oLigneEtat.m_bPlein)
			{
				// Et on demande au cache de la mettre a jour si besoin
				oLigneEtat.m_bPlein = this.bMAJLigne(i);
			}

			// Compte le nombre de lignes OK
			if (oLigneEtat.m_bPlein)
			{
				nNumPlein++;
			}
		}

		// Affiche eventuellement le masque
		this.AfficheMasque(nNumPlein != nLimiteI, false);
	},

	AfficheMasque:function (bVisible, bMasqueTransparent)
	{
		// Affiche la petite image de chargement
		this.AfficheLoading();

		// Si il y a des requetes de tri en attente dans le cache : force le masque
		if (this.m_oCache.bAvecRequeteTri())
			bVisible = true;

		// Si l'etat du masque ne change pas on ne fait rien pour ne pas changer les animations
		if (this.m_bMasqueVisible == bVisible)
			return;

		// Sauve l'etat du masque
		this.m_bMasqueVisible = bVisible;

		// Affiche les masques qui vont bien
		this.AfficheMasques(bMasqueTransparent);

		// Si on ne force pas le masque d'attente alors on se note de l'afficher peut etre dans quelques instants
		if (bMasqueTransparent)
		{
			this.nSetTimeoutTable("AfficheMasques", 1000)
		}
	},

	// Affiche les masque si besoin
	AfficheMasques:function (bMasqueTransparent)
	{
		// Affiche les masques qui vont bien
		this.AfficheUnMasque(this.ID_MASQUE, this.m_bMasqueVisible && !bMasqueTransparent);
		// Masque transparent
		this.AfficheUnMasque(this.ID_MASQUETRANSPARENT, this.m_bMasqueVisible && bMasqueTransparent);
	},

	// Affiche un masque
	AfficheUnMasque:function (sIdMasque, bVisible)
	{
		// Recupere le masque
		var oMasque = this.oGetTableId(sIdMasque);

		// On deplace enventuellement le champ si il va etre visible
		if (bVisible)
		{
			oMasque.style.left = oMasque.parentNode.scrollLeft;
			// Et on le redimensionne aussi pour qu'il prenne la place disponible
//			oMasque.style.width = oMasque.parentNode.clientWidth;
//			oMasque.style.height = oMasque.parentNode.clientHeight;
			oMasque.style.width = oMasque.parentNode.offsetWidth;
			oMasque.style.height = oMasque.parentNode.offsetHeight;
		}
		else
		{
			// On remet a gauche pour eviter les probleme de zone vide a droite
			oMasque.style.left = 0;
		}

		// Si on n'a pas toutes les lignes ou s'il y a des requetes en cours => L'affiche
		oMasque.style.visibility = bVisible ? "inherit" : "hidden";
	},

	AfficheLigne:function (oLigneEtat, bAffiche)
	{
		// Partie commune des IDs
		oLigneEtat.m_oLigne.style.visibility = bAffiche ? "inherit" : "hidden";
		// Et on stocke la visibilite que l'on met
		oLigneEtat.m_bVisible = bAffiche;
	},

	// Recupere les valeurs de la zone de l'ascenseur en tenant compte du facteur multiplicatif
	nGetAscenseurParentScrollTop:function ()
	{
		return this.m_oAscenseurParent.scrollTop * this.m_nFacteurAscenseur;
	},

	// Affecte le déplacement de l'ascenseur en différé
	SetAscenseurParentScrollTop:function (nNouvellePosition)
	{
		this.nSetTimeoutTable("SetAscenseurParentScrollTopCallBack", 500, nNouvellePosition);
	},
	
	// Affecte le déplacement de l'ascenseur
	SetAscenseurParentScrollTopCallBack:function (nNouvellePosition)
	{
		this.m_oAscenseurParent.scrollTop = nNouvellePosition;
	},
	
	// Redimensionne l'ascenseur si besoin
	RedimAscenseur:function ()
	{
		// Met a jour le flag de debordement en largeur
		this.m_bDebordeLargeur = (this.m_oDivPos.parentNode.scrollWidth > this.m_oDivPos.parentNode.clientWidth);

		// Si l'ascenseur horizontal a ete force : il faut en tenir compte
		if ((this.m_oDivPos.parentNode.style.overflowX == "scroll") || (this.m_oDivPos.parentNode.style.overflow == "scroll"))
			this.m_bDebordeLargeur = true;

		// Si la ZR est vide => on trouve scrollWidth a 0 donc on a m_bDebordeLargeur a vrai
		if (this.m_nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_ZONEREPETEE)
		{
			this.m_bDebordeLargeur = false;
		}

		var sNouvelleHauteur = "";

		// Si la table deborde
		if (this.m_bDebordeLargeur)
		{
			// Reduit le div de l'ascenseur vertical si besoin
			var oConteneurDroite = this.m_oAscenseurParent.parentNode;
			sNouvelleHauteur = (oConteneurDroite.parentNode.clientHeight - 18) + "px";
		}
		else
		{
			var oConteneurDroite = this.m_oAscenseurParent.parentNode;
			sNouvelleHauteur = "100%";
		}
//		oConteneurDroite.style.height = sNouvelleHauteur;
		// On ne change pas la taille sinon cela déplace l'ascenseur
		if (this.m_oAscenseurParent.style.height != sNouvelleHauteur)
		{
			this.m_oAscenseurParent.style.height = sNouvelleHauteur;
		}
	},

	// Met a jour la bare de defilement et les lignes visible a l'ecran
	MAJAscenseur:function (bForceMAJ)
	{
		// Redimensionne l'ascenseur si besoin
		this.RedimAscenseur();

		// Si on force alors on place la ligne au debut
		if (bForceMAJ)
			this.m_oDivPos.style.top = "0px";

		// Force le zero deplacement au parent de la position au pixel
		this.m_oDivPos.parentNode.scrollTop = "0px";

//		// Decalage a cause du debordement en largeur
//		var nDecalageDebordement = this.m_bDebordeLargeur ? 18 : 0;

		// On calcule la nouvelle hauteur si besoin
//		var nNouvelleHauteur = Math.floor((this.m_oAscenseurParent.clientHeight * this.m_nNbLignes) / this.m_nNbLignesPage + nDecalageDebordement);
		var nNouvelleHauteur = Math.floor(this.m_nHauteurLigne * this.m_nNbLignes);
		// Si zero ligne ou probleme de nombre de lignes
		if (nNouvelleHauteur <= 0) nNouvelleHauteur = 1;
		// Si la nouvelle hauteur est trop importante pour le navigateur : on utilise un facteur d'echelle
		this.m_nFacteurAscenseur = Math.ceil(nNouvelleHauteur / this.m_nLimiteHauteur);
		if (this.m_nFacteurAscenseur > 1)
			nNouvelleHauteur = Math.floor(nNouvelleHauteur / this.m_nFacteurAscenseur);

		// On change la hauteur si besoin
		var sNouvelleHauteur = nNouvelleHauteur + "px";
		if ((this.m_oAscenseur.style.height != sNouvelleHauteur) || bForceMAJ)
		{
			this.m_oAscenseur.style.height = sNouvelleHauteur;

			// Defini la position de la scroll bar en fonction du debut
			var nNouvellePosition = Math.floor((this.m_nDebut * this.m_nHauteurLigne + parseInt(this.m_oDivPos.style.top)) / this.m_nFacteurAscenseur);
			if (this.m_oAscenseurParent.scrollTop != nNouvellePosition)
			{
//				this.m_oAscenseurParent.scrollTop = nNouvellePosition;
				this.SetAscenseurParentScrollTop(nNouvellePosition);
			}
		}

		// Et met si besoin l'evenement sur le scrolling
		if (!this.m_oAscenseurParent.onscroll)
		{
			this.m_oAscenseurParent.onscroll = this.m_fScroll;
		}

		// Deplace eventuellement la table si besoin (En cas de fichier raccourci)
		if ((this.m_nDebut > 0) && (this.m_nNbLignes - this.m_nDebut < this.m_nNbLignesPage))
		{
			var nDebut = this.m_nNbLignes - this.m_nNbLignesPage;
			if (nDebut < 0)
				nDebut = 0;
			this.SetDebut(nDebut);

			// Deplace la table => Provoque un rappel a nous meme mais comme on s'est modifie pour ne plus verifier notre condition
			// Pas plus de un appel recursif
			this.DeplaceTable();
		}
	},

	// Deplacement de la table
	DeplaceTable:function ()
	{
		// Finie la saisie si besoin
		if (this.m_oGestionSaisie.m_bSaisie)
		{
			this.m_oGestionSaisie.SaisieFin(true, false);
		}

		// Force le zero deplacement au parent de la position au pixel
		this.m_oDivPos.parentNode.scrollTop = "0px";

		// Defini la position du debut en fonction de la Ascenseur
		var nNouveauDebut = Math.floor(this.nGetAscenseurParentScrollTop() / this.m_nHauteurLigne);

		// La barre de defilement n'est pas exacte sous IE.
//		// Pour la derniere page, S'il y a beaucoup de ligne on peu etre a une position non exacte (Genre 4 points au dessus de la derniere ligne)
//		if (nNouveauDebut + this.m_nNbLignesPage == this.m_nNbLignes - 1)
//		{
//			// Ne met pas le facteur d'echelle dans le calcul
//			var dPosition = this.nGetAscenseurParentScrollTop() / this.m_nHauteurLigne;
//			if (Math.ceil(dPosition) - dPosition <= 0.1)
//			{
//				nNouveauDebut++;
//			}
//		}

		// Deplace au pixel le div de la table
//		this.m_oDivPos.style.top = Math.floor((((nNouveauDebut * this.m_oAscenseurParent.clientHeight) / this.m_nNbLignesPage) - this.nGetAscenseurParentScrollTop()) / this.m_nFacteurAscenseur) + "px";
		this.m_oDivPos.style.top = Math.floor((nNouveauDebut * this.m_nHauteurLigne) - this.nGetAscenseurParentScrollTop()) + "px";

		// Si on n'a pas bouger : fini
		if (nNouveauDebut == this.m_nDebut)
		{
			return;
		}

		// Sinon defini de debut
		this.SetDebut(nNouveauDebut);

//		// Met a jour la Ascenseur
//		this.MAJAscenseur();

		// Invalide les lignes et les reaffiche si elles sont disponible
		var bLignesInvalides = false;
		var bAfficheMasque = false;
		var i = 0;
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			// Reaffiche la ligne
			var bPlein = this.bMAJLigne(i);
			this.m_tabLignesEtat[i].m_bPlein = bPlein;
			// Et si on a pas la ligne on le note pour redemander les valeurs
			if ((!bPlein) && this.m_oCache.bDansPlageCache(this.m_nDebut + i))
			{
				bLignesInvalides = true;
			}
			// Vide la ligne si besoin
			if ((!bPlein) && (this.m_nDebut + i < this.m_nNbLignes))
			{
				// Affiche le masque
				bAfficheMasque = true;
			}
		}

		// Affiche le masque si besoin
		this.AfficheMasque(bAfficheMasque, false);

		// Vide le cache invalide
		this.m_oCache.SupprimeLignesInutiles();

		// Annule un precedent setTimeout si besoin
		if (this.m_nRequeteDiffereeTimeOutId != undefined)
		{
			clearTimeout(this.m_nRequeteDiffereeTimeOutId);
			delete this.m_nRequeteDiffereeTimeOutId;
		}

		// Si le cache est vide => C'est que l'on a un gros deplacement
		// => Effectue une requete differe pour ne pas en faire 50
		if (this.m_oCache.m_nDebutCache == -1)
		{
			// Remplit les variables
			this.m_bRequeteDiffereeForce = bLignesInvalides;

			// Et lance la requete en differe (300ms)
			this.m_nRequeteDiffereeTimeOutId = this.nSetTimeoutTable("RemplitCacheDiff", 300);
		}
		else
		{
			// Si besoin demande du cache
			this.m_oCache.RemplitCache(this.m_nDebut, bLignesInvalides, -1, this.m_bSensTri);
		}

		// Et met a jour l'affichage
		this.MAJLignes();
	},

	// Fonction de requete differee
	RemplitCacheDiff:function ()
	{
		// Supprime l'ID
		delete this.m_nRequeteDiffereeTimeOutId;

		// Demande du cache
		this.m_oCache.RemplitCache(this.m_nDebut, this.m_bRequeteDiffereeForce);
		delete this.m_bRequeteDiffereeForce;
	},

	// Gestion du redimensionnement d'une colonne
	// On recoit cette evenement lors du click
	OnRedimCol:function (oEvent, nColonne, nTailleMinimale)
	{
		// Appel notre gestionnaire de redimensionnement
		this.m_oRedimColonnes.OnMouseDown(oEvent, nColonne, nTailleMinimale);
	},

	// Lien sur une colonne lien
	OnColonneLien:function (nLigneAbsolue, nColonne, oEvent)
	{
		// Pour l'instant : rebondi sur la selection simple de ligne
		this.OnSelectLigne(nLigneAbsolue - this.m_nDebut, nColonne, oEvent, true);
	},

	// Actualise l'affichage du tri de colonne
	AfficheTriColonne:function (nColonne, bInverse)
	{
		var oTmp = this;
		// Vire le flag de tri de la colonne trie
		if (this.m_nColonneTrie != -1)
		{
			var oAncienneImage = this.oGetTableId(this.ID_TITRE, this.ID_TRI, this.m_nColonneTrie);
			oAncienneImage.src = this.m_tabImgTri[0];
			// Et restaure la fonction de click
			var nOldColonneTrie = this.m_nColonneTrie;
			oAncienneImage.onclick = function () { oTmp.OnTriColonne(nOldColonneTrie, 0); };
		}

		// Memorise notre colonne comme trie
		this.m_nColonneTrie = nColonne;
		this.m_bSensTri = !bInverse;

		// Affiche l'image
		var oImage = this.oGetTableId(this.ID_TITRE, this.ID_TRI, this.m_nColonneTrie);
		oImage.src = bInverse ? this.m_tabImgTri[2] : this.m_tabImgTri[1];
		// Et modifie la fonction de click
		oImage.onclick = bInverse ? function () { oTmp.OnTriColonne(nColonne, 0); } : function () { oTmp.OnTriColonne(nColonne, 1); }
	},

	// Valide une ligne de table et envoie la modification au serveur
	OnValideLigneZR:function (sCelluleId, nLigneAbsolue, bChangement)
	{
		// Si pas de changement : fini
		if (!bChangement && !this.m_bValideLigneZRChangement)
			return;

		// Memorisation du changement
		this.m_bValideLigneZRChangement = true;

		// Pour les gestion du champ qui prend le focus on se fait un setTimeout (1ms)
		// Comme ca le onfocus a le temps de traiter le cas et de nous bloquer
		// Seulement si on n'a pas deja un timeout
		if (!this.m_nValideLigneZRTimeOutId)
		{
			this.m_sValideLigneZRCelluleId = sCelluleId;
			this.m_nValideLigneZRLigneAbsolue = nLigneAbsolue;
			this.m_nValideLigneZRTimeOutId = this.nSetTimeoutTable("_OnValideLigneZR", 1);
		}
	},

	// Valide une ligne de table et envoie la modification au serveur
	_OnValideLigneZR:function ()
	{
		// Recuperation et suppression des proprietes
		var sCelluleId = this.m_sValideLigneZRCelluleId;
		delete this.m_sValideLigneZRCelluleId;
		var nLigneAbsolue = this.m_nValideLigneZRLigneAbsolue;
		delete this.m_nValideLigneZRLigneAbsolue;
		delete this.m_bValideLigneZRChangement;
		delete this.m_nValideLigneZRTimeOutId;

		// Recupere le formulaire a la main
		var tabContenu = this.tabGetElements(oGetId(sCelluleId));

		// Et envoi la requete
		if (tabContenu)
		{
			this.m_oCache.CreeRequeteModifLigne(nLigneAbsolue, true, tabContenu);
		}
	},

	// Recupere tous les elements formulaire d'une ligne de zone repetee
	tabGetElements:function (oCellule)
	{
		var tabElements = new Array();

		if (oCellule)
		{
			// Ajoute dans le tableau les 4 types de champ formulaire
			// La valeur retournee par getElementsByTagName n'est pas un tableau donc :
			// tabElements = tabElements.concat(oCellule.getElementsByTagName("XXX")); ne fonctionne pas

			tabElements = this._tabGetUnTypeElement(oCellule, "BUTTON", tabElements);
			tabElements = this._tabGetUnTypeElement(oCellule, "INPUT", tabElements);
			tabElements = this._tabGetUnTypeElement(oCellule, "SELECT", tabElements);
			tabElements = this._tabGetUnTypeElement(oCellule, "TEXTAREA", tabElements);
		}

		return tabElements;
	},

	// Fusionne le retour de getElementsByTagName avec un tableau
	_tabGetUnTypeElement:function (oCellule, sTag, tabElements)
	{
		// Trouve les elements
		var tabElementsByTagName = oCellule.getElementsByTagName(sTag);
		// Fusionne les tableaux
		var i = 0;
		var nLimiteI = tabElementsByTagName.length;
		for (i = 0; i < nLimiteI; i++)
		{
			tabElements.push(tabElementsByTagName[0]);
		}
		// Et retourne le tableau resultat
		return tabElements;
	},

	// Indique qu'une ligne donnee de la ZR a recu le focus
	OnFocusLigneZR:function (sCelluleId, nLigneAbsolue, nNumElement)
	{
		// Si on a un traitement de focus qui correspond : on le bloque
		if (this.m_nValideLigneZRTimeOutId && (this.m_sValideLigneZRCelluleId == sCelluleId) && (this.m_nValideLigneZRLigneAbsolue == nLigneAbsolue))
		{
			clearTimeout(this.m_nValideLigneZRTimeOutId);
			delete this.m_sValideLigneZRCelluleId;
			delete this.m_nValideLigneZRLigneAbsolue;
			// Ne supprime pas le flag de changement
			// delete this.m_bValideLigneZRChange
			delete this.m_nValideLigneZRTimeOutId;
		}

		if (!this.m_bSansLimite)
		{
			var nDefilement = 0;
			// Si la ligne est au debut et a moitie visible ou a la fin : force le defilement
			if ((nLigneAbsolue == this.m_nDebut) && (parseInt(this.m_oDivPos.style.top) < 0))
			{
				nDefilement = parseInt(this.m_oDivPos.style.top);
			}
			else if (nLigneAbsolue >= (this.m_nDebut + this.m_nNbLignesPage))
			{
				nDefilement = parseInt(this.m_oAscenseur.style.height) / this.m_nNbLignes;
			}

			// Si on a un defilement
			if (nDefilement != 0)
			{

//				this.nForceDefilement(nDefilement);

				// Et note de redonner le focus
//				this.DonneFocusZR(sCelluleId, nNumElement);
				this.nSetTimeoutTable("DonneFocusZR", 20, nDefilement + ",\"" + sCelluleId + "\"," + nNumElement);
			}
		}
	},

	// Redonne le focus a un champ de la ZR sur une ligne donnee
	DonneFocusZR:function (nDefilement, sCelluleId, nNumElement)
	{
		this.nForceDefilement(parseInt(nDefilement));
		var tabContenu = this.tabGetElements(oGetId(sCelluleId));
		// Donne le focus au champ
		if (tabContenu && tabContenu[nNumElement])
		{
			tabContenu[nNumElement].focus();
		}
	},

	// Force le defilement de la ZR
	// Renvoi le deplacement reel
	nForceDefilement:function (nDeplacement)
	{
		// Si pas de possibilite de defilement : fini
		if (parseInt(this.m_oAscenseur.style.height) <= this.m_oAscenseurParent.clientHeight)
			return 0;
			
		var noAnciennePosition = this.m_oAscenseurParent.scrollTop;
		// Deplace l'ascenseur
		if (this.m_oAscenseurParent.scrollTop + nDeplacement < 0)
			this.m_oAscenseurParent.scrollTop = 0;
		else if (this.m_oAscenseurParent.scrollTop + nDeplacement > parseInt(this.m_oAscenseur.style.height) - this.m_oAscenseurParent.clientHeight)
			this.m_oAscenseurParent.scrollTop = parseInt(this.m_oAscenseur.style.height) - this.m_oAscenseurParent.clientHeight;
		else
			this.m_oAscenseurParent.scrollTop += nDeplacement;
		this.DeplaceTable();

		// Renvoie le deplacement reel
		return (this.m_oAscenseurParent.scrollTop - noAnciennePosition);
	},

	// Click sur une ligne de zone repetee
	OnSelectLigneZR:function (nLigneRelative, oEvent)
	{
		// Si on est sans selection => Sort direct
		if (this.m_nTypeSelection == WDTable.prototype.SELECTION_SANS) return;
		// Si on a une requete en cours : la modification de la selection va etre ecrase donc on l'interdit
		if (this.m_oCache.m_tabRequetes.length > 0) return;

		var nLigneAbsolue = nLigneRelative + this.m_nDebut;
		// Si on a changer la selection
		var oLigne = this.m_tabLignesEtat[nLigneRelative].m_oLigne;

		// Regarde si la ligne est selectionne et ne fait rien si c'est deja le cas
		if (this.nLigneSelectionne(nLigneAbsolue, oEvent) != -1)
			return;

		// Supprime les autres selections (Normalement une seule ligne)
		this.bLigneDeselectionneTous(-1);

		// Selectionne la ligne
		this.bLigneSelectionne(nLigneAbsolue, true, oEvent);

//		// Envoie une requete au serveur demandant le refraichissement de la ligne donnee
//		this.RequeteSelection(-1);
	},

	// Click sur une ligne
	OnSelectLigne:function (nLigneRelative, nColonne, oEvent, bColonneLien)
	{
		// Si on est sans selection => Sort direct
		if (this.m_nTypeSelection == WDTable.prototype.SELECTION_SANS) return;
		// Si on a une requete en cours : la modification de la selection va etre ecrase donc on l'interdit
		if (this.m_oCache.m_tabRequetes.length > 0) return;

		var bCtrl = (oEvent && !bColonneLien) ? oEvent.ctrlKey : false;
		var bMaj = (oEvent && !bColonneLien) ? oEvent.shiftKey : false;

		var nLigneAbsolue = nLigneRelative + this.m_nDebut;
		// Si on a changer la selection
		var bChangementSelection = false;
		var oLigne = this.m_tabLignesEtat[nLigneRelative].m_oLigne;

		// Selection simple ?
		var bSelectionSimple = (this.m_nTypeSelection == WDTable.prototype.SELECTION_SIMPLE) || bColonneLien;

		// Si on est en selection simple ou que ctrl et shift ne sont pas enfonce (Selection multiple): supprime les autres selections
		// Sauf la ligne en cours de selection car elle doit rester selectionnee
		if (bSelectionSimple || ((bCtrl == false) && (bMaj == false)))
		{
			bChangementSelection = this.bLigneDeselectionneTous(nLigneAbsolue);
		}

		// Regarde si la ligne est selectionne
		nIndiceTableauSelectionActuelle = this.nLigneSelectionne(nLigneAbsolue, oEvent);

		// Decide si on va deselectionne la ligne si elle est selectionne
		if (nIndiceTableauSelectionActuelle != -1)
		{
			// bCtrl est a faux si la colonne est lien donc c'est OK
			if ((bSelectionSimple || (this.m_nTypeSelection == WDTable.prototype.SELECTION_MULTIPLE)) && bCtrl)
			{
				// Deselectionne la ligne
				bChangementSelection = this.bLigneDeselectionne(nIndiceTableauSelectionActuelle);
			}
			// Sinon ne fait rien : on laisse la ligne selectionnee
		}
		else
		{
			// Dans le cas de la selection multiple on traite les touches shift et cltrl
			// Ctrl => Rien a faire on ajoute simplement a la selection
			// Shfit => On ajoute tout entre la premiere selectionne si elle existe et la nouvelle
			if (!bSelectionSimple && (bMaj == true))
			{
				if (this.m_tabSelection.length > 0)
				{
					var i = this.m_tabSelection[0];
					var nSens = (i < nLigneAbsolue) ? 1 : -1;
					for (i = this.m_tabSelection[0] + nSens; i != nLigneAbsolue; i += nSens)
					{
						// Selectionne les lignes si besoin
						bChangementSelection |= this.bLigneSelectionne(i, true, oEvent);
					}
				}
			}

			// Selectionne la ligne
			bChangementSelection |= this.bLigneSelectionne(nLigneAbsolue, true, oEvent);
		}

		// Gere une eventuelle entree en saisie dans la ligne
		this.m_oGestionSaisie.OnClickCellule(nLigneRelative, nColonne);

		// Envoie une requete au serveur demandant le refraichissement de la ligne donnee si il y a eu une nouvelle selection
		if ((bChangementSelection || bColonneLien) && (this.m_bRetourServeurSelection))
			this.RequeteSelection(bColonneLien ? nColonne : -1);
	},

	// Gestionnaire du tri des colonnes
	OnTriColonne:function (nColonne, bInverse)
	{
		// Affiche le picto dans la colonne de tri
		this.AfficheTriColonne(nColonne, bInverse);

		// Lance une requete pour le tri en bloquant l'affichage
		this.m_oCache.RemplitCache(this.m_nDebut, false, nColonne, this.m_bSensTri);
		// Marque toutes les lignes comme invalide
		var i = 0;
		var nLimiteI = this.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.m_tabLignesEtat[i].m_bPlein = false;
		}
		// Et affiche le masque qui empeche la saisie
		this.AfficheMasque(true, false);
	},

	// Deplacement de la souris au dessus d'une image de recherche
	OnRechercheSouris:function(nColonne, oImg, bSourisOver)
	{
		// Si on est sur la colonne en cours de recherche on laisse l'image qui va bien
		if (nColonne == this.m_nColonneRecherche)
		{
			return true;
		}

		oImg.src = this.m_tabImgRecherche[bSourisOver ? 1 : 0];
		return true;
	},

	OnRechercheColonne:function (nColonneRecherche, oImgOrigine)
	{
		// Supprime l'ancinne recherche si besoin (Arrive dans des combinaisons d'evenements bizarres)
		this.OnAnnuleRechercheColonne();

		// Objet qui servira pour placer le formulaire
		var oParent = oImgOrigine.parentNode;

		// Cree dynamiquement la zone de recherche
		var oFormRecherche = document.createElement("FORM");
		// Avec son action
		oFormRecherche.method = "POST";
		oFormRecherche.action = "javascript:return false;";
		// Et la methode de validation qui renvoie toujours faux pour ne pas valider le formulaire
		var oTmp = this;
		// Attention ! Dans les fonctions, this désigne le formulaire et pas l'objet externe courant
		if (bIE)
			oFormRecherche.onsubmit = function () { oTmp.OnValideRechercheColonne(this.elements[0].value); if (event.returnValue) event.returnValue = false; return false; };
		else
			oFormRecherche.onsubmit = function (event) { oTmp.OnValideRechercheColonne(this.elements[0].value); if (event.preventDefault) event.preventDefault(); return false; };
		// Puis on place le formulaire au bon endroit
		oFormRecherche.style.position = "absolute";
		oFormRecherche.style.top = "0px";
		oFormRecherche.style.left = "0px";
		oFormRecherche.style.width = (oParent.offsetWidth - oImgOrigine.offsetWidth - 1) + "px";
		oFormRecherche.style.height = "100%";

		// Ensuite on fait le champs de saisie
		var oSaisieRecherche = document.createElement("INPUT");
		oSaisieRecherche.type = "TEXT";
		// On l'attache a son parent
		oFormRecherche.appendChild(oSaisieRecherche);
		// Et gere la perte du focus
		if (bIE)
			oSaisieRecherche.onblur = function () { oTmp.OnAnnuleRechercheColonne(); if (event.returnValue) event.returnValue = false; return false; };
		else
			oSaisieRecherche.onblur = function (event) { oTmp.OnAnnuleRechercheColonne(); if (event.preventDefault) event.preventDefault(); return false; };
		// Et lui defini son style
		oSaisieRecherche.style.position = "absolute";
		oSaisieRecherche.style.top = "0px";
		oSaisieRecherche.style.left = "0px";
		oSaisieRecherche.style.width = "100%";
		oSaisieRecherche.style.height = "100%";
		oSaisieRecherche.style.borderWidth = "0";
		oSaisieRecherche.style.borderStyle = "solid";
		// Si la hauteur du parent est faible : reduit la taille de la police
		if ((oParent.offsetHeight <= 18) && (oParent.offsetHeight > 0))
		{
			oSaisieRecherche.style.fontSize = Math.max(oParent.offsetHeight - 4, 6) + "px";
		}

		// On met le tout dans la cellule de recherche
		this.m_oFormRecherche = oParent.appendChild(oFormRecherche);

		// On affiche l'image de recherche
		oImgOrigine.src = this.m_tabImgRecherche[2];

		// Et donne le focus au champ de saisie
		this.m_oFormRecherche.elements[0].focus();

		// Colonne de recherche
		this.m_nColonneRecherche = nColonneRecherche;
		this.m_oImgOrigine = oImgOrigine;
		// On supprime la gestion de la perte du survol pour ne pas remettre la mauvaise image
		this.m_fOnMouseDown = oImgOrigine.onmousedown;

		var oTmp2 = this.m_oFormRecherche;
		if (bIE)
			oImgOrigine.onmousedown = function () { oTmp.OnValideRechercheColonne(oTmp2.elements[0].value); if (event.returnValue) event.returnValue = false; return false; };
		else
			oImgOrigine.onmousedown = function (event) { oTmp.OnValideRechercheColonne(oTmp2.elements[0].value); if (event.preventDefault) event.preventDefault(); return false; };
	},

	// Valide la recherche et l'effectue
	OnValideRechercheColonne:function (sValeurRecherche)
	{
		// Seulement si on a une recherche et que la valeur recherchee est non vide)
		if ((this.m_oFormRecherche) && (sValeurRecherche.length > 0))
		{
			this.m_oCache.CreeRequeteRecherche(this.m_nColonneRecherche, sValeurRecherche);

			// Marque toutes les lignes comme invalide
			var i = 0;
			var nLimiteI = this.m_tabLignesEtat.length;
			for (i = 0; i < nLimiteI; i++)
			{
				this.m_tabLignesEtat[i].m_bPlein = false;
			}
			// Et affiche le masque qui empeche la saisie
			this.AfficheMasque(true, true);
		}

		// Supprime le HTML de la recherche
		this.OnAnnuleRechercheColonne();
	},

	// Annule la recherche dans une colonne
	OnAnnuleRechercheColonne:function ()
	{
		// Seulement si on a deja une recherche en cours : on la supprime
		if (this.m_oFormRecherche)
		{
			// Supprime le onblur de l'element de saisie pour eviter que le navigateur (IE) ne s'emelle les pinceau si on creer un nouvek objet
			this.m_oFormRecherche.elements[0].onblur = null;

			// Detache et supprime le formulaire du document
			this.m_oFormRecherche = this.m_oFormRecherche.parentNode.removeChild(this.m_oFormRecherche);
		}

		// Restaure l'etat de l'image
		if (this.m_oImgOrigine)
		{
			// L'image originale
			this.m_oImgOrigine.src = this.m_tabImgRecherche[0];
			// Et de click
			if (this.m_fOnMouseDown)		{ this.m_oImgOrigine.onmousedown	= this.m_fOnMouseDown; }
		}

		// Supprime nos membres
		delete this.m_nColonneRecherche;
		delete this.m_oFormRecherche;
		delete this.m_oImgOrigine;
		delete this.m_fOnMouseDown;
	},

	// Indique le nombre de colonne
	nNbColonnes: function ()
	{
		return this.m_tabColonnes.length;
	},

	// Indique si la colonne donnee est saisisable
	bColonneSaisissable:function (nColonne)
	{
		// Si la colonne n'est pas dans le tableau => Pas saisissable
		if ((nColonne >= this.m_tabColonnes.length) || (nColonne < 0))
		{
			return false;
		}

		// Si la colonne est lien elle n'est pas saisissable
		if (this.m_tabColonnes[nColonne].nLien != "0") return false;

		// Sinon on renvoie la valeur dans le tableau
		return this.m_tabColonnes[nColonne].m_bSaisissable;
	},

	// Indique si la colonne donnee est lien
	nColonneLien:function (nColonne)
	{
		// Si la colonne n'est pas dans le tableau => Pas saisissable
		if ((nColonne >= this.m_tabColonnes.length) || (nColonne < 0))
		{
			return WDColonne.prototype.nLien;
		}

		// Sinon on renvoie la valeur dans le tableau
		return this.m_tabColonnes[nColonne].nLien;
	},

	// Indique l'etat de la colonne donnee lien
	nColonneEtatLien:function (nColonne)
	{
		// Si la colonne n'est pas dans le tableau => Pas saisissable
		if ((nColonne >= this.m_tabColonnes.length) || (nColonne < 0))
		{
			return WDColonne.prototype.nLienEtat;
		}

		// Sinon on renvoie la valeur dans le tableau
		return this.m_tabColonnes[nColonne].nEtatLien;
	},

	// Indique le type de la colonne
	nColonneType:function (nColonne)
	{
//		// Si la colonne n'est pas dans le tableau => colonne normale
//		if ((nColonne >= this.m_tabColonnes.length) || (nColonne < 0))
//		{
//			return WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE;
//		}

		// Sinon on renvoie la valeur dans le tableau
		return this.m_tabColonnes[nColonne].m_nType;
	},

	// Renvoie le tableau des options d'une colonne COMBO
	tabColonneCombo:function (nColonne)
	{
		// Si la colonne n'est pas dans le tableau => tableau vide
		if ((nColonne >= this.m_tabColonnes.length) || (nColonne < 0))
		{
			return WDColonne.prototype.tabOptions;
		}

		// Sinon on renvoie la valeur dans le tableau
		return this.m_tabColonnes[nColonne].tabOptions;
	},

	// Indique que l'on a change la valeur de la colonne donnee
	ChangementValeur:function (nLigneRelatif, nColonne, sValeur, nValeur)
	{
		// Par securite si la colonne n'est pas saisissable => Fini (Normalement cela n'arrive pas
		if (this.bColonneSaisissable(nColonne) == false) return;

		// On met la valeur dans le cache et on le marque comme modifie
		this.m_oCache.ChangementValeur(this.m_nDebut + nLigneRelatif, nLigneRelatif, nColonne, sValeur, nValeur);
	},

	// Demande la MAJ de la ligne relative
	bMAJLigne:function (nLigneRelatif)
	{
		return this.m_oCache.bMAJLigne(this.m_nDebut + nLigneRelatif, nLigneRelatif);
	},

	// Demande la MAJ de la ligne absolue
	MAJLigne:function (nLigneAbsolue)
	{
		this.m_tabLignesEtat[nLigneAbsolue - this.m_nDebut].m_bPlein = this.m_oCache.bMAJLigne(nLigneAbsolue, nLigneAbsolue - this.m_nDebut);
	},

	// Et notifie le serveur de la validation d'une ligne
	ValideChangement:function (nLigneRelatif)
	{
		this.m_oCache.ValideChangement(this.m_nDebut + nLigneRelatif);
	},

	// Supprime les lignes virtuelles
	SupprimeLignesVirtuelles:function ()
	{
		this.m_oCache.SupprimeLignesVirtuelles();
	},

	// Cree la ligne virtuelle si besoin
	CreeLigneVirtuelle:function (nLigneRelatif)
	{
		this.m_oCache.CreeLigneVirtuelle(this.m_nDebut + nLigneRelatif);
	},

	// Recupere le contenu d'une cellule
	sGetCellule:function (nLigneRelatif, nColonne, bPourEntier)
	{
		return this.m_oCache.sGetCellule(this.m_nDebut + nLigneRelatif, nColonne, bPourEntier);
	}
};

// Objet pour la gestion de la saisie
function WDSaisieCellule (oObjetTable)
{
	this.m_oObjetTable = oObjetTable;	// Sauve l'objet attache
};

WDSaisieCellule.prototype =
{
	m_nLigne:				-1,			// Pas de ligne
	m_nColonne:				-1,			// Pas de colonne
//	m_oDateClick:			null,		// Et pas de date
//	m_bSaisie:				false,		// Pas de saisie en cours
//	m_oFormSaisie			null,		// Pas de formulaire de saisie pour le moment
	m_nDiffClick:			1000,		// Au mimimun un double click en 1 seconde
	m_fFunctioNull:			function(){},

	// Gestion du click sur une cellule
	OnClickCellule:function (nLigne, nColonne)
	{
		// Si la colonne est une colonne interrupteur => Ne fait rien. L'interrupteur se gere tout seul
		var nType = this.m_oObjetTable.nColonneType(nColonne)
		if ((nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR) || (nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE))
		{
			// Si on est en saisie : fini la saisie
			if (this.m_bSaisie)
			{
				this.SaisieFin(true, false);
			}
			return;
		}

		// Si on est sur la meme ligne
		if (this.m_nLigne == nLigne)
		{
			// Si on est sur la meme cellule
			if (this.m_nColonne == nColonne)
			{
				// Si on est deja en saisie : ignore l'evenement : c'est que l'utilisateur a clicke dans la boite de saisie
				if (this.m_bSaisie) return;

				// Si la difference de date est assez courte : commence la saisie
				if ((new Date()).getTime() - this.m_oDateClick.getTime() < this.m_nDiffClick)
				{
					// Valide et commence la saisie
					this.SaisieCommence();
					return;
				}

				// Sinon met simplement a jour comme pour les autres cas
			}
			else
			{
				// Si on est en saisie : fini la saisie et change de case
				if (this.m_bSaisie)
				{
					this.SaisieFin(true, true, nColonne);
					return;
				}
			}
		}

		// Si on est en saisie : fini la saisie
		if (this.m_bSaisie)
		{
			this.SaisieFin(true, false);
		}

		// On supprime la precedente interception du double clic
		var oCellulePrecedente = this.m_oObjetTable.oGetTableId(this.m_nLigne, this.m_nColonne);
		if (oCellulePrecedente)
		{
			oCellulePrecedente.ondblclick = this.m_fFunctioNull;
		}

		// Met a jour pour l'edition suivante
		this.m_nLigne = nLigne;
		this.m_nColonne = nColonne;
		this.m_oDateClick = new Date();

		// On intercepte le double clic qui lance directement la saisie si besoin
		var oTmp = this;
		this.m_oObjetTable.oGetTableId(this.m_nLigne, this.m_nColonne).ondblclick = function () { if (oTmp.m_bSaisie) return; oTmp.SaisieCommence(); };
	},

	// Valide et commence la saisie
	SaisieCommence:function ()
	{
		// Fin de l'intercepte du double clic
		this.m_oObjetTable.oGetTableId(this.m_nLigne, this.m_nColonne).ondblclick = this.m_fFunctioNull;

		// Verifie que la colonne est saisisable
		if (!this.m_oObjetTable.bColonneSaisissable(this.m_nColonne))
		{
			// Supprime nos membres
			this.m_nLigne = -1;
			this.m_nColonne = -1;
			delete this.m_oDateClick;
			delete this.m_bSaisie;

			// Et sort direct
			return;
		}

		// Cree la ligne virtuelle si besoin
		this.m_oObjetTable.CreeLigneVirtuelle(this.m_nLigne);

		// Entre en saisie
		// Objet qui servira pour placer le formulaire
		var oParent = this.m_oObjetTable.oGetTableId(this.m_nLigne, this.m_nColonne);

		// Cree dynamiquement la zone de recherche
		var oFormSaisie = document.createElement("FORM");
		// Avec son action
		oFormSaisie.method = "POST";
		oFormSaisie.action = "javascript:return false;";
		// Et la methode de validation qui renvoie toujours faux pour ne pas valider le formulaire
		var oTmp = this;
		if (bIE)
			oFormSaisie.onsubmit = function () { oTmp.SaisieFin(true, true, -1); if (event.returnValue) event.returnValue = false; return false; };
		else
			oFormSaisie.onsubmit = function (event) { oTmp.SaisieFin(true, true, -1); if (event.preventDefault) event.preventDefault(); return false; };

		// Puis on place le formulaire au bon endroit
//		oFormSaisie.style.position = "absolute";
//		oFormSaisie.style.top = "0px";
//		oFormSaisie.style.left = "0px";
		oFormSaisie.style.width = "100%";
//		oFormSaisie.style.height = "100%";
		oFormSaisie.style.height = this.m_oObjetTable.m_nHauteurLigne + "px";

		// Ensuite on fait le champs de saisie
		var oSaisie;
		// Regarde si la colonne est d'un type acceptable
		var nType = this.m_oObjetTable.nColonneType(this.m_nColonne);
		switch (nType)
		{
		// Combo
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO:
			var nValeurCombo = this.m_oObjetTable.sGetCellule(this.m_nLigne, this.m_nColonne, true);

			oSaisie = document.createElement("SELECT");
			// Ajoute les options
			var tabOptions = this.m_oObjetTable.tabColonneCombo(this.m_nColonne);
			var i;
			var nLimiteI = tabOptions.length;
			for (i = 0; i < nLimiteI; i++)
			{
				oSaisie.options[i] = new Option(tabOptions[i], i);
				if (nValeurCombo == i)
					oSaisie.options[i].selected = true;
				// Il y a un problemes avec les carteres > 127
				oSaisie.options[i].innerHTML = clWDEncode.sEncodeInnerHTML(oSaisie.options[i].innerHTML, false, true);
			}
			// Donne un ID au champ de saisie
			oSaisie.id = oParent.id + "_SAI";
			break;

		// Interrupteur
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR:
			break;

		// Image => Pas saissisable
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE:
			break;

		// Saisie et autres
		case WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE:
		default:
			var sValeurCellule = this.m_oObjetTable.sGetCellule(this.m_nLigne, this.m_nColonne);
			oSaisie = document.createElement("INPUT");
			oSaisie.type = "TEXT";
			// On n'oublie pas la valeur
			oSaisie.value = clWDEncode.sEncodeCharset(sValeurCellule, false);
			// Donne un ID au champ de saisie
			oSaisie.id = oParent.id + "_SAI";
			break;
		}

		// Et gere la perte du focus et la touche echap
		if (bIE)
		{
			oSaisie.onblur = function () { oTmp.SaisieFin(true, false); if (event.returnValue) event.returnValue = false; return false; };
			oSaisie.onkeydown = function ()
			{
				switch (event.keyCode)
				{
				case 9:		// Tab
					oTmp.SaisieFin(true, true, -1);
					break;
				case 27:	// Escape
					oTmp.SaisieFin(false);
					break;
				default:
					return;
				}
				if (event.returnValue) event.returnValue = false;
				return false;
			};
		}
		else
		{
			oSaisie.onblur = function (event) { oTmp.SaisieFin(true, false); if (event.preventDefault) event.preventDefault(); return false; };
			oSaisie.onkeydown = function (event)
			{
				switch (event.keyCode)
				{
				case 9:		// Tab
					oTmp.SaisieFin(true, true, -1);
					break;
				case 27:	// Escape
					oTmp.SaisieFin(false);
					break;
				default:
					return;
				}
				if (event.preventDefault) event.preventDefault();
				return false;
			};
		}
		if (nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO)
			oSaisie.onchange = oSaisie.onblur;
		// Et lui defini son style
//		oSaisie.style.position = "absolute";
//		oSaisie.style.top = "0px";
//		oSaisie.style.left = "0px";
		oSaisie.style.width = "100%";
//		oSaisie.style.height = "100%";
		oSaisie.style.height = this.m_oObjetTable.m_nHauteurLigne + "px";
//		// Ainsi que la couleur pour faire joli
//		// Recupere la couleur de fond
		oSaisie.style.borderWidth = "0";
		oSaisie.style.borderStyle = "solid";

		// Pour la couleur de fond on remonte au parent qui n'est pas un div
		var oParentTable = oParent;
		while ((oParentTable.tagName == "DIV") && (oParentTable.style.backgroundColor == "")) oParentTable = oParentTable.parentNode;
		oSaisie.style.borderColor = _JGCS(oParentTable).backgroundColor;

		// Si la hauteur du parent est faible : reduit la taille de la police
		if ((oParentTable.offsetHeight <= 18) && (oParentTable.offsetHeight > 0))
		{
			oSaisie.style.fontSize = Math.max(oParentTable.offsetHeight - 4, 6) + "px";
		}

		// On vide le contenu du parent pour que le positionnement soit bon
		while (oParent.childNodes.length)
			oParent.removeChild(oParent.childNodes[0]);

		// On l'attache a son parent
		oFormSaisie.appendChild(oSaisie);

		// Sauve le handler de click de la cellule parent
		this.m_fOldClick = oParentTable.onclick;
		oParentTable.onclick = null;
		this.m_oParentTable = oParentTable;

		// On met le tout dans la cellule de recherche
		this.m_oFormSaisie = oParent.appendChild(oFormSaisie);

		// Et donne le focus au champ de saisie
		this.m_oFormSaisie.elements[0].focus();
		// Et le redonne (La premiere methode ne fonctionne pas dans certains cas)
		if (nType == WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE)
			setTimeout("oGetId('" + oParent.id + "_SAI" + "').focus()", 1);
	},

	// Fin de la saisie
	// bValide : Valide ou annule la saisie
	// bContinueSaisie (Uniquemenet si bValide = vrai) : Indique si on doit essayer de continuer la saisie
	// nColonne (Uniquemenet si bValide et bContinue Saisie sont vrai) : Numero de la colonne ou donner le focus
	SaisieFin:function (bValide, bContinueSaisie, nColonne)
	{
		// Fin de l'intercepte du double clic
		this.m_oObjetTable.oGetTableId(this.m_nLigne, this.m_nColonne).ondblclick = this.m_fFunctioNull;

		// Seulement si on a deja une recherche en cours : on la supprime
		if (this.m_oFormSaisie)
		{
			// Supprime le onblur de l'element de saisie pour eviter que le navigateur (IE) ne s'emelle les pinceau si on creer un nouvek objet
			this.m_oFormSaisie.elements[0].onblur = null;
			this.m_oFormSaisie.elements[0].onkeypress = null;

			if (bValide)
			{
				// Sauve la valeur
				var sValeur = "";
				var nValeur = -1;

				// Transformations selon le type du champ
				var nType = this.m_oObjetTable.nColonneType(this.m_nColonne);
				switch (nType)
				{
				// Combo
				case WDAJAXMain.prototype.XML_CHAMP_TYPE_COMBO:
					var sSelection = this.m_oFormSaisie.elements[0].value;
					nValeur = parseInt(sSelection);
					sValeur = this.m_oObjetTable.tabColonneCombo(this.m_nColonne)[nValeur];
					break;

				// Interrupteur, image, saisie et autres => rien
				case WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR:
//					var tabElements = document.getElementsByName("_" + (this.m_oObjetTable.m_nDebut + this.m_nLigne) + "_" + this.m_oObjetTable.m_sAliasTable + "_" + this.m_nColonne);
//					if ((tabElements.length > 0) && (tabElements[0].checked))
//					sValeur = ((tabElements.length > 0) && (tabElements[0].checked)) ? "1" : "0";
//					nValeur = ((tabElements.length > 0) && (tabElements[0].checked)) ? 1 : 0;
					break;

				case WDAJAXMain.prototype.XML_CHAMP_TYPE_IMAGE:
					break;

				case WDAJAXMain.prototype.XML_CHAMP_TYPE_SAISIE:
				default:
					sValeur = this.m_oFormSaisie.elements[0].value;
					break;
				}
				this.m_oObjetTable.ChangementValeur(this.m_nLigne, this.m_nColonne, sValeur, nValeur);

				// Si on doit tenter de continuer la saisie
				if (bContinueSaisie)
				{
					// Si on a -1 : tente la colonne saisissable suivante
					if (nColonne == -1)
					{
						var i;
						var nLimiteI = this.m_oObjetTable.nNbColonnes();
						for (i = this.m_nColonne + 1; i < nLimiteI; i++)
						{
							var nType = this.m_oObjetTable.nColonneType(i)
							if (nType != WDAJAXMain.prototype.XML_CHAMP_TYPE_INTERRUPTEUR)
							{
								if (this.m_oObjetTable.bColonneSaisissable(i))
								{
									// On va reprende a cette colonne
									nColonne = i;
									break;
								}
							}
						}
					}
				}

				// Si on a pas une colonne saisissable ou que l'on indique de ne pas continuer : valide le changement
				if (!bContinueSaisie || !this.m_oObjetTable.bColonneSaisissable(nColonne))
				{
					// Et notifie le serveur
					this.m_oObjetTable.ValideChangement(this.m_nLigne);
				}
			}

			// Detache et supprime le formulaire du document
			this.m_oFormSaisie = this.m_oFormSaisie.parentNode.removeChild(this.m_oFormSaisie);

			// Restaure le handler de click de la cellule parent
			this.m_oParentTable.onclick = this.m_fOldClick;
			delete this.m_fOldClick;
			delete this.m_oParentTable;

			// Et on demande la MAJ de la ligne
			this.m_oObjetTable.bMAJLigne(this.m_nLigne);

			// Supprime nos membres
			delete this.m_oFormSaisie;
			// Si on a une colonne saisissable et que l'on n'a pas envoyer les changements : donne le
			if (bContinueSaisie && this.m_oObjetTable.bColonneSaisissable(nColonne) && (nColonne != -1))
			{
				// Memorise la colonne
				this.m_nColonne = nColonne;

				// Et commence la saisie
				this.SaisieCommence();
				return;
			}
			else
			{
				// Fait le menage
				this.m_nLigne = -1;
				this.m_nColonne = -1;
				delete this.m_oDateClick;
				delete this.m_bSaisie;
			}
		}

		// Supprime la ligne virtuelle
		this.m_oObjetTable.SupprimeLignesVirtuelles();
	}
};

// Objet pour la gestion du redimensionnement des colonnes
function WDRedimColonnes (oObjetTable)
{
	this.m_oObjetTable = oObjetTable;	// Sauve l'objet attache

	// Cree les fonctions de rappel
	var oTmp = this;
	this.m_fMouseMove = function(oEvent) { oTmp.OnMouseMove.call(oTmp, oEvent ? oEvent : event); };
	this.m_fMouseUp = function(oEvent) { oTmp.OnMouseUp.call(oTmp, oEvent ? oEvent : event); };
	this.m_fSelectStart = function(oEvent) { return false; };		// Ne fonctionne que avec IE

	var m_oLastRedim = null;
};

WDRedimColonnes.prototype =
{
//	m_nColonne:				-1,			// Pas de colonne
//	m_nPosX:				-1,			// Pas de position souris
//	m_nTailleOriginale:		-1,			// Et pas de taille originale
//	m_nTailleMinimale:		-1,			// Taille minimale
//	m_fOldMouseMove:		undefined,	// Creer les pointeur vers les anciennes fonctions de la page
//	m_fOldMouseUp:			undefined,
//	m_fOldSelectStart:		undefined,
//	m_bRedimEffectue:		false,		// Indique si on a redimensionne une colonne
//	m_tabColonnesTaille:	new Array()	// Indique les % de tailles des colonnes redimensionnees

	// Sauve la largeurs des colonnes dans un tableau et renvoie le tableau
	// Si bProportionnel est a vrai ne sauve que les colonnes proportionnelles
	tabSauveLargeurColonnes:function (bProportionnel)
	{
		var tabLargeurColonnes = new Array();

		var i = 0;
		// Recupere le DIV
		var oCelluleColonne = this.m_oObjetTable.oGetTableId(0, i);
		var nLimiteI = this.m_oObjetTable.nNbColonnes();
		while (oCelluleColonne || (i < nLimiteI))
		{
			if (oCelluleColonne)
			{
				// Recherche le TD
				while (oCelluleColonne.tagName != "TD") oCelluleColonne = oCelluleColonne.parentNode;

				// Si la colonne est invisible : ne sauve pas sa valeur : elle ne sera pas manipuler
				var oStyleCelluleColonne = _JGCS(oCelluleColonne);
				var sVisibility = oStyleCelluleColonne.visibility;
				if (((sVisibility == "visible") || (sVisibility == "inherit")) && (oStyleCelluleColonne.display != "none"))
				{
					// Lit la taille si demande
					if (((oCelluleColonne.width + "").indexOf("%") != -1) || !bProportionnel)
					{
						// Stocke la valeur proportionnelle dans le cas proportionnel
						if (bProportionnel)
						{
							tabLargeurColonnes[i] = oCelluleColonne.width;
							this.SetDimColonne(i, oCelluleColonne.offsetWidth);
						}
						else
						{	// Sinon stocke la vrai valeur
							tabLargeurColonnes[i] = oCelluleColonne.offsetWidth;
						}
					}
				}
			}

			// Passe a la colonne suivante
			i++;
			oCelluleColonne = this.m_oObjetTable.oGetTableId(0, i);
		}

		// Renvoi le tableau forme
		return tabLargeurColonnes;
	},

	// Restaure la largeurs des colonnes depuis le tableau donne
	RestaureLargeurColonnes:function (tabLargeurColonnes)
	{
		// Restaure les colonnes
		var i = 0;
		var nLimiteI = tabLargeurColonnes.length;
		for (i = 0; i < nLimiteI; i++)
		{
			if (tabLargeurColonnes[i] !== undefined)
			{
				// Restaure la taille de la colonne
				this.SetDimColonne(i, tabLargeurColonnes[i]);
			}
		}
	},

	// Si on est sur le premier redimensionnement des colonnes : memorise la taille des colonnes et fixe les largeurs
	ColonnesFige:function ()
	{
		//assert(!this.m_bRedimEffectue);

		this.m_bRedimEffectue = true;

		this.m_sTableTaille = this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TABLEINTERNE).width;
		var oTitrePosPixel = this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TITRE, this.m_oObjetTable.ID_POSITION_PIXEL);
		this.m_sTableTitreTaille = oTitrePosPixel.width;

		// Detecte les colonnes ancrees et memorise leur pourcentage d'ancrage
		this.m_tabColonnesTaille = this.tabSauveLargeurColonnes(true);

		// Supprime la taille de la table parent
		this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TABLEINTERNE).width = "";
		this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TITRE, this.m_oObjetTable.ID_POSITION_PIXEL).width = "";
	},

	// Restaure les largeurs variables de colonnes
	ColonnesRestaure:function ()
	{
		// On n'a besoin de le faire seulement si un redimensionnement a ete effectue
		if (this.m_bRedimEffectue)
		{
			// Restaure la largeur des colonnes ancrables
			this.RestaureLargeurColonnes(this.m_tabColonnesTaille);

			var oTitrePosPixel = this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TITRE, this.m_oObjetTable.ID_POSITION_PIXEL);
			// Restaure la taille de la table
			this.m_oObjetTable.oGetTableId(this.m_oObjetTable.ID_TABLEINTERNE).width = this.m_sTableTaille;
			oTitrePosPixel.width = this.m_sTableTitreTaille;

			// Supprime les membres
			delete this.m_bRedimEffectue;
			delete this.m_tabColonnesTaille;
			delete this.m_sTableTaille;
			delete this.m_sTableTitreTaille;
		}
	},

	// Appel lors du debut d'un click pour le redimensionnement
	// Pose les hooks
	OnMouseDown:function (oEvent, nColonne, nTailleMinimale)
	{
		// Si on est sur le premier redimensionnement des colonnes : memorise la taille des colonnes et fixe les largeurs
		if (!this.m_bRedimEffectue)
		{
			this.ColonnesFige();
		}

		// Commence par sauver le numero de colonne
		this.m_nColonne = nColonne;

		// Sauve la position souris
		this.m_nPosX = oEvent.clientX;

		// Sauve la taille originale
		this.m_nTailleOriginale = parseInt(this.m_oObjetTable.oGetTableId(0, nColonne).parentNode.style.width);

		// Sauve la taille minimale
		this.m_nTailleMinimale = nTailleMinimale;

		// Sauve les hooks du document en evitant de se hooker deux fois
		// Et pose les notres
		if (document.onmousemove != this.m_fMouseMove)
		{
			// Sur le deplacement de la souris
			this.m_fOldMouseMove = document.onmousemove;
			document.onmousemove = this.m_fMouseMove;
		}
		if (document.onmouseup != this.m_fMouseUp)
		{
			// Sur le relachement de la souris
			this.m_fOldMouseUp = document.onmouseup;
			document.onmouseup = this.m_fMouseUp;
		}
		if (document.onselectstart != this.m_fSelectStart)
		{
			// Et sur la selection dans le document
			this.m_fOldSelectStart = document.onselectstart;
			document.onselectstart = this.m_fSelectStart;
		}
	},

	// Appel lors du deplacement de la souris
	OnMouseMove:function (oEvent)
	{
		// On laisse le temp du rafraichissement
		if (this.m_oLastRedim && ((new Date()).getTime() - this.m_oLastRedim.getTime() < 50))
		{
			return;
		}

		// Rappele la fonction sauve
		if (typeof this.m_fOldMouseMove == "function")
		{
			this.m_fOldMouseMove(oEvent);
		}

		// Calcule la nouvelle taille des colonnes
		// ATTENTION : Si on est en affichage de droite a gauche il faut inverser le deplacement de la souris car
		// les coordonnees X sont toujours de gauche a droite
		var nNouvelleTaille = 0;
		if (document.dir == "rtl")
			nNouvelleTaille = -(oEvent.clientX - this.m_nPosX) + this.m_nTailleOriginale;
		else
			nNouvelleTaille = oEvent.clientX - this.m_nPosX + this.m_nTailleOriginale;

		if (nNouvelleTaille < 8)
		{	// Taille minimum de 8
			nNouvelleTaille = 8;
		}

		// Respecte la taille minimale
		if ((this.m_nTailleMinimale != -1) && (nNouvelleTaille < this.m_nTailleMinimale))
		{
			nNouvelleTaille = this.m_nTailleMinimale;
		}

		// Defini la taille de la colonne
//		this.SetDimColonne(this.m_nColonne, nNouvelleTaille + "px");
		this.SetDimColonne(this.m_nColonne, nNouvelleTaille);

		// On memorise pour le du rafraichissement
		this.m_oLastRedim = new Date();

		// Si on risque de devoir changer le nombre de lignes
		if (this.m_oObjetTable.bMAJNbLignesVisibles(true, true))
		{
			// Sauve la largeur de toutes les colonnes
			var tabLargeurColonnes = this.tabSauveLargeurColonnes(false);

			// Rafraichi l'ascenseur et les lignes de la table. Si le nombre de ligne change : il faut reppliquer la gestion de la largeur
			if (this.m_oObjetTable.bMAJNbLignesVisibles(true, false))
			{
				// Restaure la largeur des colonnes
				this.RestaureLargeurColonnes(tabLargeurColonnes);
			}
		}
	},

	// Defini la taille de toute une colonne
	SetDimColonne:function (nColonne, sNouvelleTaille)
	{
		var sSuffixeStyle = ((sNouvelleTaille + "").indexOf("%") != -1) ? "" : "px";

		// Defini la taille de la colonne de titre
//		this.SetDimCellule(this.m_oObjetTable.ID_TITRE, nColonne, sNouvelleTaille, 3, sSuffixeStyle);
		this.SetDimCellule(this.m_oObjetTable.ID_TITRE, nColonne, sNouvelleTaille, 2, sSuffixeStyle);
		// Defini la taille de la colonne cote donnees
		var i = 0;
		var nLimiteI = this.m_oObjetTable.m_tabLignesEtat.length;
		for (i = 0; i < nLimiteI; i++)
		{
			this.SetDimCellule(i, nColonne, sNouvelleTaille, 2, sSuffixeStyle);
		}
	},

	// Definit la taille d'une cellule
	SetDimCellule:function (nLigne, nColonne, sNouvelleTaille, nNiveau, sSuffixeStyle)
	{
		// Recupere le DIV
		var oDiv = this.m_oObjetTable.oGetTableId(nLigne, nColonne);
		// Des des cas rares (Avec 0 lignes et sans repro) on a oDiv a null
		// Cela doit venir de this.m_oObjetTable.m_tabLignesEtat.length qui n'est pas a 0
		// => Blindage
		if (!oDiv) return;

//		while (nNiveau-- > 0)
		while (--nNiveau > 0)
		{
			// On un niveau de DIV de plus
			oDiv = oDiv.parentNode;

			// Pour le parent du DIV (Autre DIV)
			oDiv.style.width = sNouvelleTaille + sSuffixeStyle;
		}
		// Puis le TD parent
		oDiv = oDiv.parentNode;
		oDiv.width = sNouvelleTaille;
	},

	// Appel lors du relachement de la souris
	// Restaure les fonctions hookees
	OnMouseUp:function (oEvent)
	{
		// Rappele la fonction sauve
		if (typeof this.m_fOldMouseUp == "function")
		{
			this.m_fOldMouseUp(oEvent);
		}

		// Restaure les hook du document
		document.onmousemove = this.m_fOldMouseMove;
		document.onmouseup = this.m_fOldMouseUp;
		document.onselectstart = this.m_fOldSelectStart;

		// Vire la sauvegarde des hooks du document
		delete this.m_fOldMouseMove;
		delete this.m_fOldMouseUp;
		delete this.m_fOldSelectStart;

		// Vire la taille originale
		delete this.m_nTailleOriginale;
		delete this.m_nTailleMinimale;

		// Vire la limitation du rafriachissement
		delete this.m_oLastRedim;

		// Vire la position souris
		delete this.m_nPosX;

		// Vire le numero de colonne
		delete this.m_nColonne;
	}
};

