var ODYFORM_VER = "$Revision: 1.155 $";

var bFORCE_ONE_STAGE = false; // si true, utilise saveEntity d'un bloc (step 5), sinon step 300&301
var sCONFIRM_MSG_DIV = "msgConfirmSave";

var sFORM_INPUT_PREFIX = "t_";
var sPARAMFORM_INPUT_PREFIX = "param_";
var sFORM_DEL_FLAG_PREFIX = "d_";
var sINPUT_ENT_EID = "hdent.eid";
var sMAIN_FORM_NAME_PREFIX = "frmMainInput";
var sTAB_FORM_NAME_PREFIX  = "frmInput";

var sATTR_ATTRIBUTE_ID  ="attr";    // id de l'attribut d'un multi
var sPARAM_ATTRIBUTE_ID ="aid"; // NB : doit être identique à la valeur du param http (QuerySvt.sPARAM_ATTRIB_ID)
var sPARAM_QRESULT_ID   = "qrid";  // histoTime d'une requete. transmis entre steps 8 et 10 de QuerySvt

var sATTR_MAIN_ENT_ID   ="mainEntId"; // id de l'entité principale, initialisé dans query_inc.xsl lors de la création du div du multi
var sPARAM_MAIN_ENT_ID  ="mainEntId"; // NB : doit être identique à la valeur du param http (QuerySvt.sPARAM_MAIN_ENTITY_ID)

/* ========================================================================
  CINEMATIQUE DES SUBMIT
  
  Client        |        query.js      |      odyform.js      |      include.js
  ___________________________________________________________

  clic
                  sendForm
                                         send
                                          |    * verif que pas de données non enregistrées (checkMulti())
                                          |
                                          |-> validateAndSubmit
                                               |    * contrôle double-soumission
                                               |
                                               |-> validate()
<à compléter>
    __________________________________
  1/ si "OneStage"
    __________________________________
  2/ si pas "OneStage"

   ======================================================================== */

// ========================================================================
// Contenu du fichier :
//
// ODYFormRegistry
// ODYForm
// ODYFormTab
// =========================================================================

// =========================================================================
// @class
// ODYFormRegistry - Registre des formulaires (singleton)
// =========================================================================
ODYFormRegistry = function() {
  if (ODYFormRegistry.caller != ODYFormRegistry.getInstance) throw new Error("ODYFormRegistry is a singleton : call 'getInstance'.");
  this.m_hForm = new Hash();
}

ODYFormRegistry.__instance__ = null;  //define the static property

ODYFormRegistry.register = function(p_oODYForm) {
  var oOFR = ODYFormRegistry.getInstance();
  // pas d'erreur, on remplace juste
  // if (oOFR.m_hForm.get(p_oODYForm.m_sName)) throw "Form '" + p_oODYForm.m_sName + "' already is in the registry";
  // alert("registering " + p_oODYForm.m_sName); 
  oOFR.m_hForm.set(p_oODYForm.m_sName, p_oODYForm);
}

//------------- getValidOrCreateODYForm ------------------
/**
 * @param p_iTS  timestamp du container
 * @param p_oCont container à considérer dans lequel sera cherché le formulaire (par défaut sera oCC si pas de timestamp fourni)
 * @return l'ODYForm correspondant au couple (nom, displayId) indiqué, qui a été soit récupéré dans la
 *  registry, soit (re)créé s'il n'existait pas ou n'était plus valide
 */
ODYFormRegistry.getValidOrCreateODYForm = function(p_sName, p_sDisplayId, p_iTS, p_oCont) {
  var oOF = ODYFormRegistry.getODYFormByFormName(p_sName);
  if (!oOF || !oOF.isValid()) {
    oOF = new ODYForm(p_sName, p_sDisplayId, p_iTS, p_oCont);

  } else { // reinit du DOMForm, l'ancien pouvant être devenu invalide (cas d'un reload par ex)
    oOF.initDOMForm(p_sName, p_iTS, p_oCont);
  }

  return oOF;
}

//------------- getODYFormByFormName ------------------
ODYFormRegistry.getODYFormByFormName = function(p_s) {
  var oOFR = ODYFormRegistry.getInstance();
  var oOF = oOFR.m_hForm.get(p_s);
  // if (oOF && !oOF.isValid()) oOF.m_oDOMForm = $O2(oOF.m_oDOMForm.id); // cause écran bleu ??
  return oOF;
}

//------------- getODYFormByDisplayId ------------------
/**
 * @return l'ODYForm dont le display sous-jacent a l'id indiqué 
 */
ODYFormRegistry.getODYFormByDisplayId = function(p_iDispId) {
  var oOFR = ODYFormRegistry.getInstance();
  var oFound;
  oOFR.m_hForm.values().each(function (oOF) { 
    if (oFound || oOF.m_iDisplayId!=p_iDispId) return;
    oFound = oOF;
  });
  return oFound;
}

//------------- getODYFormByEntityId ------------------
/**
 * @return l'ODYForm dont le display sous-jacent affiche l'entité ayant l'id indiqué
 */
ODYFormRegistry.getODYFormByEntityId = function(p_iEntiId) {
  var oOFR = ODYFormRegistry.getInstance();
  var oFound;
  oOFR.m_hForm.values().each(function (oOF) { 
    if (oFound || oOF.m_iEntityId!=p_iEntiId) return;
    oFound = oOF;
  });
  return oFound;
}

//------------- getODYFormBySubFormName ------------------
/**
 * @return l'ODYForm dont un des onglets contient un formulaire (<form>) qui a pour id (et donc pour name) la chaine indiquée 
 */
ODYFormRegistry.getODYFormBySubFormName = function(p_s) {
  var oOFR = ODYFormRegistry.getInstance();
  var oFound;
  oOFR.m_hForm.values().each(function (oOF) { 
    if (oFound || !oOF.m_moTab) return;
    oOF.m_moTab.each(function(oOFT) {
      if (oFound) return;
      if (oOFT.m_oDOMForm && oOFT.m_oDOMForm.id==p_s) oFound = oOF;
    })
  });
  return oFound;
}

ODYFormRegistry.showForms = function () {
  alert(ODYFormRegistry.getInstance().toString());
}

ODYFormRegistry.getInstance = function () {
  if (this.__instance__ == null) this.__instance__ = new ODYFormRegistry();
  return this.__instance__;
}

ODYFormRegistry.makeForm = function(p_oForm) {
  Object.extend(p_oForm, ODYForm);
  return p_oForm;
}

//------------- toString ------------------
ODYFormRegistry.prototype.toString = function() {
  var s = "";
  var i = 0;
  var oOFR = ODYFormRegistry.getInstance();
  oOFR.m_hForm.values().each(function (oOF) { s += oOF.toString() + "\n\n"; i++; });
  s = i + " forms :\n" + "------------\n" + s;
  return s;
}

// =========================================================================
// @class
// ODYForm - Un formulaire dans Odyssée
//
// @param 
// : p_sName  le nom du formulaire principal (celui de la frame "button" dans le cas d'un formulaire d'édition multi-onglets)
// =========================================================================

// ---------- <constructor> ----------------
/**
 * @param p_iTS  timestamp du container
 * @param p_oCont container à considérer dans lequel sera cherché le formulaire (par défaut sera oCC si pas de timestamp fourni)
 */
ODYForm = function(p_sName, p_sDisplayId, p_iTS, p_oCont) {
  this.m_sName = p_sName;
  this.m_iDisplayId = p_sDisplayId;  // l'id du display sous-jacent au formulaire
  this.initDOMForm(p_sName, p_iTS, p_oCont);
  this.m_moTab = null;
  this.m_bReadOnly = false;
  // infos de navigation : step et frame cibles + paramètre "following" pour spécifier une action à enchainer après soumission
  this.m_iStep =null;
  this.m_iStep1 =300;   // step de pré-vérification + "commit" ssi oneStage
  this.m_iStep2 =301;   // step de "commit" (ssi pas en oneStage)
  this.m_iStep3 =null;  // step d'affichage final
  this.m_sTargetFrm =null;
  this.m_sFollowing =null;

  // infos sur le type et l'entité éditée
  this.m_iEntityId = null;
  this.m_iETId = null;
  // données pour le validator
  this.m_oConfig = {
      'to_disable' : ['Submit', 'Reset'],
      'alert' : 1
    };
  this.m_msField = null;
  this.m_msDoublonFields = null;
  // inscription automatique dans la registry
  ODYFormRegistry.register(this);
  this.m_hAdditionalParam = null;
}

//************************* STATIC ********************************
// séparateur pour valeurs multiples dans champ de formulaire
ODYForm.sVALUE_SEP = ";";
ODYForm.sET_SN = "ODYForm";
ODYForm.sFOND_GRIS_DIV_ID = "fondGris";
ODYForm.sACTION_FORM_DIV_ID = "actionFormDiv";

//------------- displayEntity ------------------
/**
 * Affiche la fiche d'une entité soit dans la div dans laquelle elle était déjà cachée, ou par un navigate sur edit.svt
 * step 10 sinon
 *
 * @param iStep      step à afficher, 10 si non précisé
 * @param iEntityId  id de l'entité à afficher
 * @param iETId      id du type de l'entité à afficher
 * @param iOrigineId id de l'origine (entityId ou searchID)
 */
ODYForm.displayEntity = function(p) {
  var oOF = ODYFormRegistry.getODYFormByEntityId(p.iEntityId);
  var sTgt = oOF ? oOF.getMainContainer().id : '_parent';
  navigate('/edit.svt', p.iStep||10, sTgt,'id=' + p.iEntityId +'&amp;etid=' + p.iETId + '&amp;o=fi&amp;oid=' + p.iOrigineId);
}

//------------- displayParentEntity ------------------
/**
 * Affiche la fiche de l'entité parente de la fiche actuelle
 *
 * @param oCaller    l'objet DOM d'où émane l'appel
 * @param iStep      step à afficher, 10 si non précisé
 */
ODYForm.displayParentEntity = function(p) {
  var oOF = p.oCaller.getContainer()[ODYForm.sET_SN];
  ODYForm.displayEntity({iStep: p.iStep, iEntityId: oOF.m_iParentPersonId, iETId:oOF.m_iParentPersonETId
    , iOrigineId:oOF.m_iEntityId});
}

// ---------- showTab ----------------
/*
 * @param oMC  MainContainer qui contient le frameset du formulaire
 * @param p_iNb  le numéro d'ordre de l'onglet à montrer [1,n]
 * @param p_iTabsSpanId  l'id (littéral) du <span> qui correspond à l'onglet (="tabs_"+<id display_tab>)
 */
ODYForm.showTab = function(oMC, p_iNb, p_iTabsSpanId) {
  var oTabCont = oMC.findChildContainerByPattern(".*tabs.*")[0];
  var oButCont = oMC.findChildContainerByPattern(".*but.*")[0];
  oTabCont.setAttribute("iVisibleTabIndex",p_iNb);
  var bIsWizard = oButCont.id.indexOf("wizard")!=-1;

  var vChildren = oMC.childNodes;
  for (var i=0,l=vChildren.length ; i<l ; i++) {
    var oDiv = vChildren[i];
    if (!oDiv) continue;
    if (oDiv.tagName!="DIV" || !oDiv.id || oDiv.id.indexOf("frame")==-1) continue;
    if (oDiv.id.match(new RegExp(".*_"+p_iNb+"(\\.\\d+)?$"))) {
      oDiv.show();
      if (!oDiv.isLoaded() && oDiv.src) display(oDiv, bIsWizard ? 60 :0, oDiv.src); 

    } else oDiv.hide();
  }

  // mise à jour de la class des tabs
  var oTbl = oTabCont.findChildById(p_iTabsSpanId);
  for (var i=1; i<=oTbl.getAttribute("tabCount"); i++) {
    var oTab = oTbl.findChildById("tab" + i);
    if (p_iNb==i) {
      addCssStyle(oTab,sCssClassTabSelected);
      removeCssStyle(oTab,sCssClassTabDeselected);
    } else {
      addCssStyle(oTab,sCssClassTabDeselected);
      removeCssStyle(oTab,sCssClassTabSelected);
    }
  }
}

//--------------  buildErrorsArray ----------------
/**
 * liste les champs en erreur
 * @param p_oXmlDoc le document XML qui contient les erreurs
 * @param p_oOF l'instance du ODYForm courant, utilisée le cas échéant pour retrouver l'input concerné du formulaire
 * @return l'Array d'objets FormError
 */
ODYForm.buildErrorsArray = function(p_oXmlDoc, p_oOF) {
  if (typeof(p_oXmlDoc)=="string") p_oXmlDoc = getDocElement(p_oXmlDoc);
  var vErrList = p_oXmlDoc.selectNodeSet("//errorl/formError");
  var moErrorList = new Array();

  if (vErrList.length>0) { // erreur de type formError
    for (var i=0, l=vErrList.length ; i<l ; i++) {
      var xError   = vErrList.item(i);
      var iDispiId = xError.getAttribute("dispiid");
      var iEntiId = xError.getAttribute("ed_id");
      var iAttrId = xError.getAttribute("aid");
      var oFrameElem = p_oOF ? p_oOF.getInputByDisplayItemId(iDispiId, iAttrId, iEntiId) : null;
      var oError = new OInputError(oFrameElem);
      if (oFrameElem) { // nb : si erreur concerne un champ d'un onglet non affiché, input non trouvé
        var oFrameForm = oFrameElem.form;
        var oLibElem = oFrameForm.findChildById("lib"+oFrameElem.id, "input");
        var oElem = oLibElem ? oLibElem : oFrameElem; // l'élément qui porte les attributs
      
        var sCurrentValue = getCurrentValue(oFrameElem); // là on garde bien oFrameElem, pas oElem : cf. impl de la méthode
        var sCurrentText = getCurrentText(oFrameElem);
        oError.m_sValue = sCurrentValue;
        oError.m_sText = sCurrentText;
        oError.m_sTabName = oFrameElem.m_oTab ? oFrameElem.m_oTab.m_sLib : "";
        var labSpan = oFrameForm.findChildById("lab_" + iDispiId, "span");
        if (labSpan) oError.m_sFieldName = labSpan.innerHTML;
      }
      oError.m_sLevel = xError.getAttribute("level");
      var xJavaError =  xError.getElementsByTagName("error").item(0);
      var xMsg = xError.getElementsByTagName("message").item(0);
      var xEnt = xError.getElementsByTagName("e");
      if (xEnt && xEnt.item(0)) {
        oError.m_iEntityId = xEnt.item(0).getAttribute("id");
        oError.m_sEntityDesignation = xEnt.item(0).getElementsByTagName("name").item(0);
      }
      var xCause = xError.getElementsByTagName("cause").item(0);
      if (xMsg && xMsg.getFirstChild()) {
        oError.m_sMsg = xMsg.getFirstChild().getNodeValue();
      } else if (xCause && xCause.getFirstChild()) {
        oError.m_sMsg = xCause.getFirstChild().getNodeValue();
      } else {
        oError.m_sMsg = xJavaError.getElementsByTagName("type").item(0).getFirstChild().getNodeValue();
          + "<br>" + xJavaError.getElementsByTagName("stack").item(0).getFirstChild().getNodeValue();
      }
      moErrorList.push(oError);
    }

  } else { // erreur système
    vErrList = p_oXmlDoc.selectNodeSet("//data/error");
    if (vErrList.length>0) {
      var xError = vErrList.item(0);
      var oError = new OInputError(oElem);
      oError.m_sType = "system";
      oError.m_sLevel = "ERROR";
      oError.m_sTabName = "Une erreur s'est produite : "; //xError.getElementsByTagName("type").item(0).getFirstChild().getNodeValue();
      var xMsg = xError.getElementsByTagName("message").item(0);
      var xCause = xError.getElementsByTagName("cause").item(0);
      if (xMsg && xMsg.getFirstChild()) {
        oError.m_sFieldName = xMsg.getFirstChild().getNodeValue();
      } else if (xCause && xCause.getFirstChild()) {
        oError.m_sFieldName = xCause.getFirstChild().getNodeValue();
      }
      oError.m_sMsg = xError.getElementsByTagName("stack").item(0).getFirstChild().getNodeValue();
      moErrorList.push(oError);
    }
  }
  return moErrorList;
}

// ---------- getFondGrisDiv ----------------
/** @return la div "fond gris" */
ODYForm.getFondGrisDiv = function(p_oParentFrm) {
  // recherche d'abord parmi les enfants
  var oDiv = p_oParentFrm.findDirectChildByExpr("div#"+ODYForm.sFOND_GRIS_DIV_ID);
  // sinon parmi tous les descendants (est-ce pertinent ??)
  if (!oDiv) oDiv = p_oParentFrm.findChildById(ODYForm.sFOND_GRIS_DIV_ID);
  if (!oDiv || oDiv.getContainer()!=p_oParentFrm) {
    oDiv = document.createElement('div');
    oDiv.id = ODYForm.sFOND_GRIS_DIV_ID;
    p_oParentFrm.appendChild(oDiv);
  }
  return $O2(oDiv);
}

// ---------- getAlertDiv ----------------
/** @return la div alerte */
ODYForm.getAlertDiv = function(p_oParentFrm, p_sDivName) {
  var nodes = p_oParentFrm.childNodes;
  var oAlertDiv = null;
  var sDivId = p_sDivName ? p_sDivName : "alerte";
  //recherche du noeud fils dont l'id est alerte --> Attention il faut rechercher sur les fils directe!
  //car possible d'avoir une div alerte rattaché à un noeud fils, celui-ci n'est pas à prendre en compte!
  for (var i=0, l = nodes.length; i <l; i++) {
      var oDOM = nodes[i];
      if (oDOM.nodeType==1 && oDOM.id==sDivId) { oAlertDiv = oDOM; break; }
  }
  
  // si pas de div trouvée ou si la div est contenue dans un sous-container => création d'une nvelle div
  if (!oAlertDiv || oAlertDiv.getContainer()!=p_oParentFrm) {
    oAlertDiv = document.createElement('div');
    oAlertDiv.id = sDivId;
    oAlertDiv.className = "alerte";
    ODYCont.makeCont(oAlertDiv);
    p_oParentFrm.appendChild(oAlertDiv);
    oAlertDiv = $O2(oAlertDiv);
    oAlertDiv.innerHTML = 
      '<div id="titre-alerte">'
    + '  <div id="titreErreur">'
    + '    <a onclick="hideErrors(window.event||arguments[0],\''+sDivId+'\')" href="#"><img id="close" align="right" src="/com/img/picto/cross.png"/></a>'
    + '    <div id="titre">Erreur de saisie</div>'
    + '  </div>'
    + '  <div id="titreValidation">Traitement</div>'
    + '</div>'
    + '<div id="msg-alerte">'
    + '  <div id="msgContent"></div>'
    + '  <div id="btnChoix" class="btnChoix">'
    + '    <span class="btnOuiNon"><a id="continue">Continuer</a></span>'
    + '    <span class="btnOuiNon"><a id="stop" onclick="hideErrors(window.event||arguments[0],\''+sDivId+'\')">Annuler</a></span>'
    + '    <span class="btnOuiNon"><a id="saveAndContinue">Enregistrer et continuer</a></span>'
    + '    <span class="btnOuiNon"><a id="discardAndContinue">Continuer sans enregistrer</a></span>'
    + '    <span class="btnOuiNon"><a id="back" onclick="hideErrors(window.event||arguments[0],\''+sDivId+'\')">Retour<br/>à la fiche</a></span>'
    + '  </div>'
    + '  <center><img id="indicator" style="display:none" src="/com/img/progress.gif" alt="Working..." /></center>'
    + '</div>';
  }
  return oAlertDiv;
}

// ---------- getQuizzDiv ----------------
/** @return la div Quizz */
ODYForm.getQuizzDiv= function(p_oParentFrm, p_sDivName) {
  var nodes = p_oParentFrm.childNodes;
  var oAlertDiv = null;
  var sDivId = p_sDivName ? p_sDivName : "quizz";
  oAlertDiv = document.createElement('div');
  oAlertDiv.id = sDivId;
  oAlertDiv.className = "quizz";
  ODYCont.makeCont(oAlertDiv);
  p_oParentFrm.appendChild(oAlertDiv);
  oAlertDiv = $O2(oAlertDiv);
  oAlertDiv.innerHTML = '<div id="msg-alerte">'
    + '  <div id="msgContent"></div>'
    + '  <div id="btnChoix" class="btnChoix">'
    + '</div>';
  return oAlertDiv;
}

// ---------- getParentFrmForAlertDiv ----------------
/** @return la frm parente de la div alert */
ODYForm.getParentFrmForAlertDiv = function(p_oCont) {
  var oButFrm    = p_oCont; // multi si table_form
  if (oButFrm.id.indexOf("ifa_")==0) {
    var oParentFrm = oButFrm; // pourquoi remonter au dessus ??  .getContainer().getContainer();

  } else if ("edi"==oButFrm.id) {
    var oParentFrm = oButFrm;

  } else if (ODYForm.sACTION_FORM_DIV_ID==oButFrm.id) {
    var oParentFrm = oButFrm;

  } else { // pour un OF "normal", on remonte au frameset == la div qui contient but + frame + tabs
    var oParentFrm = oButFrm.getContainer() ? oButFrm.getContainer() : oButFrm;
  }
  return $O2(oParentFrm);
}

// ---------- doShowButtonAlertDivForErrors ----------------
/**
 * @param p_sFollowingExpr action à mener sur le bouton "continuer". Si pas d'erreur ni warning mais au moins un
 *        message, l'action par défaut "ODYNavigation.contextBack()" est utilisée si aucune indiquée
 * @return la div des boutons
 */
ODYForm.doShowAlertDivButtonsForErrors = function(p_moErrorList,p_oAlertDiv, p_sFollowingExpr) {
  var oBtnDiv = p_oAlertDiv.findChildById("btnChoix","div");
  var oPbContinue = oBtnDiv.findChildById("continue","a");
  this.doShowAlertDivButtons(oBtnDiv);  
  
  var iAfficheCrAutoControl = 0;
  var iLevelErrorCount = 0;
  var iLevelInfoCount = 0;
  var iLevelWarningCount = 0;
  var iRefresh = 0;
  if (null!=p_moErrorList) {
  	for (var i=0, l=p_moErrorList.length ; i<l ; i++) {
   		var oError = p_moErrorList[i];
    	if ("ERROR"==oError.m_sLevel) iLevelErrorCount++;
    	else if ("INFO"==oError.m_sLevel) {
    		iLevelInfoCount++;
    		if (oError.m_sMsg.indexOf("-----")!=-1) iAfficheCrAutoControl=1;
    	}
    	else if ("WARNING"==oError.m_sLevel) iLevelWarningCount++;
    	else if ("REFRESH"==oError.m_sLevel) {
      		iRefresh++;
      		iLevelErrorCount++;
    	}
    }
    
  } else { //Dans le cas ou le message ne contient pas d'erreur (doShowMessageAgency et doShowEndAction)
  	 iLevelErrorCount = 0;
  	 iLevelInfoCount = 1;
  	 iLevelWarningCount = 0;
  }
  
  if (iLevelErrorCount==0) { // pas d'erreur
    var sFollowing = p_sFollowingExpr;
    if (iLevelInfoCount>0 && iLevelWarningCount==0) { // il existe au moins un msg info et pas de warning
      // si pas de following, contextBack() par défaut
      if (!sFollowing) sFollowing = "ODYNavigation.contextBack()";
      // si juste info, pas de "annuler" sauf s'il y a une p_sFollowingExpr (ex : liste des clients qui vont etre transférés)
      oBtnDiv.findChildById("stop","a").style.display = (p_sFollowingExpr ? "" : "none");
      p_oAlertDiv.findChildById("close","img").style.display = "none";
    }
    // si on a un following, on affiche le bouton "Continuer", sinon on le masque
    if (sFollowing) {
      if (iAfficheCrAutoControl==1) {
        var sMsg = "J\'engage ma responsabilité sur les informations du message affiché précédemment";
      	oPbContinue.listenTo("click", new Function("e", "if (confirm('"+sMsg.escapeJS("'")+"')) return navWithIndicator(this,'" + sFollowing.escapeJS("'") + "')"), false);
      	
      } else {
      	oPbContinue.listenTo("click", new Function("e", "return navWithIndicator(this,'" + sFollowing.escapeJS("'") + "')"), false);
      }
      oPbContinue.style.visibility = "";
    } else oPbContinue.style.visibility = "hidden";
    
  } else oPbContinue.style.visibility = "hidden"; // au moins une erreur : on masque le bouton "Continuer"
  oBtnDiv.style.visibility = ""; 
  return oBtnDiv;
}

// ---------- onActionFormContinue ----------------
/** Action exécutée lors d'un clic sur "Continuer" d'un action form
 */
ODYForm.onActionFormContinue = function(e, p_sExpr) {
  var oODYF = ODYFormRegistry.getODYFormByFormName('actionForm');
  oODYF.validate();
  if (oODYF.moErrorList.length>0) { oODYF.doShowErrors();return false; }
  var sSubmitData = formData2QueryString(oODYF.m_oDOMForm);
  eval(p_sExpr);
  return false;
}

// ---------- doShowButtonAlertDiv ----------------
/** affiche la barre de boutons en montrant "continuer" et "annuler" uniquement
 * et en nettoyant les éventuels listeners associés
 */
ODYForm.doShowAlertDivButtons = function(p_oBtnDiv) {
  p_oBtnDiv.style.display = "";
  var oPbContinue = p_oBtnDiv.findChildById("continue","a");
  oPbContinue.clearListeners();
  oPbContinue.style.display = "";
  oPbContinue.style.visibility = "";
  var oPbStop     = p_oBtnDiv.findChildById("stop","a");
  oPbStop.clearListeners();
  oPbStop.style.display = "";
  oPbStop.style.visibility = "";
  // cache les boutons "save & continue"
  p_oBtnDiv.findChildById("saveAndContinue"   ,"a").style.display = "none";
  p_oBtnDiv.findChildById("discardAndContinue","a").style.display = "none";
  p_oBtnDiv.findChildById("back"              ,"a").style.display = "none";
}

//--------------  doShowErrors ----------------
ODYForm.doShowErrors = function(p_moErrorList, p_oCont, p_sFollowingExpr) {
  if (!p_moErrorList.length>0) return;
  var oParentFrm = this.getParentFrmForAlertDiv(p_oCont);
  if (!oParentFrm.visible()) throw "doShowErrors : parent frame '" + oParentFrm.id + "' is not visible."; 
  var oAlertDiv = ODYForm.getAlertDiv(oParentFrm);
  
  // masque titre "enregistrement données" et affiche titre "Erreur"
  oAlertDiv.findChildById("titreErreur","div").style.display = "";
  oAlertDiv.findChildById("titreValidation","div").style.display = "none";
  
  var iRefresh = 0;
  for (var i=0, l=p_moErrorList.length ; i<l ; i++) {
    var oError = p_moErrorList[i]; 
    if ("REFRESH"==oError.m_sLevel) {
      iRefresh++;
    }
  }

  if (iRefresh > 0) {
    p_oCont.getContainer().reload();
    //lors du rechargement de la fiche, les div change! affichage du message dans la frame edi pour parer au pb
    oParentFrm = $O2('edi');
    oAlertDiv = ODYForm.getAlertDiv(oParentFrm);
  
    // masque titre "enregistrement données" et affiche titre "Erreur"
    oAlertDiv.findChildById("titreErreur","div").style.display = "";
    oAlertDiv.findChildById("titreValidation","div").style.display = "none";
  }
  // on affiche la barre de boutons
  var sFollowingExpr = p_sFollowingExpr;
  // dans le cas d'un affichage d'erreurs, p_sFollowingExpr sera exécutée hors du contexte de NavigateIfNoError, donc oRep
  // n'est pas défini et par ailleurs doit être mis à null sinon les erreurs seront traitées une 2ème fois dans sendStage2
  if (sFollowingExpr && sFollowingExpr.indexOf("oRep")!=-1) {
    sFollowingExpr = sFollowingExpr.replace(/oRep = /,"");
    sFollowingExpr = sFollowingExpr.replace(/oRep/,"null");
  }
  this.doShowAlertDivButtonsForErrors(p_moErrorList,oAlertDiv, sFollowingExpr);

  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.style.visibility = "";
  var sHTML = "";
  var oFondDiv = ODYForm.getFondGrisDiv(oParentFrm);
  var sPrevTab = "";
  for (var i=0, l=p_moErrorList.length ; i<l ; i++) {
    var oError = p_moErrorList[i];
    oAlertDiv.findChildById("titre","div").innerHTML = ("system"==oError.m_sType) ? "Erreur système" : "Attention !";
    sHTML += oError.buildHTML(sPrevTab);
    sPrevTab = oError.m_sTabName;
    if (oError.m_oInput) {
      removeCssStyle(oError.m_oInput, "mandatory"); // sinon hasError ne se voit pas
      addCssStyle(oError.m_oInput, "hasError");
      //mettre le focus sur la zone qui est en erreur
      oError.m_oInput.focus();
    }
  }
  oMsgDiv.innerHTML = sHTML;
  ODYForm.showAndCenterAlert(oParentFrm, oFondDiv, oAlertDiv);
}

//--------------  doShowActionForm ----------------
/**
 * Méthode STATIQUE pour l'affichage d'un action form
 *
 * @param p.oCont : container de l'action form
 * @param p.sHTML
 * @param p.sAction
 * @param p.iEntId
 * @param p.sFormTitle
 * @param p.sFormName
 * @param p.iMultiToRefresh
 * @param p.sFollowing
 * @param p.sFollowingSecondStep
 */
ODYForm.doShowActionForm = function(p) {

  var oParentFrm = this.getParentFrmForAlertDiv(p.oCont);
  var oAlertDiv = ODYForm.getAlertDiv(oParentFrm, ODYForm.sACTION_FORM_DIV_ID);
  
  // positionne et affiche titre du formulaire d'action
  var oAlertTitle = oAlertDiv.findChildById("titre","div");
  if (p.sFormTitle) oAlertTitle.innerHTML = p.sFormTitle; 
  oAlertTitle.style.display = "";
  // masque "enregistrement données" et affiche titre alerte
  oAlertDiv.findChildById("titreValidation","div").style.display = "none";
  
  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.style.visibility = "";
  var sHTML = p.sHTML ? p.sHTML : "";
  if (sHTML != "") {
    sHTML = '<form name="' + p.sFormName + '" id="' + p.sFormName + '">' + sHTML;
    sHTML += '</form>';
  }
  var oFondDiv = ODYForm.getFondGrisDiv(oParentFrm);
  
  if (oMsgDiv) {
    oMsgDiv.innerHTML = sHTML;
    var oOF = ODYFormRegistry.getValidOrCreateODYForm(p.sFormName, null, null, oAlertDiv);
    var vElement = oOF.m_oDOMForm.elements;
    for (i=0; i < vElement.length;i++) { 
      var oElement = vElement[i];
      if (oElement.type=="select-one") processList(oElement.getAttribute("id"), oElement.getAttribute("src"),oElement.getAttribute("current"));
      var iDataType = oElement.getAttribute("dataTypeId");
      var iDispiType = oElement.getAttribute("dispitypeid");

      if (iDataType==3) { // si type "date", insertion du calendrier
        var sInputName = oElement.getAttribute("name");
        var sCalName = 'oCal' + sInputName;
        var sCalendarDiv = '<div id=cal' + sInputName + ' class="actionFormCalendar"></div>'
          + '<img src="/com/popcalendar/cal.gif" id="imgCal' + sInputName 
          + '" onclick=\'o2Cal(this,"' + sInputName + '"); return false;\' />';
        var sScript = '{ var ' + sCalName + ' = new CalendarPopup("cal' + sInputName + '");'
          + sCalName + '.setYearSelectStartOffset(100);'
          + sCalName + '.showNavigationDropdowns();'
          + sCalName + '.showYearNavigation();}';
        var oTd = findParentByTagName(oElement, "td");
        oTd.innerHTML += sCalendarDiv;
        eval(sScript);

      } else if (iDispiType==20) {
        var sSfx = oElement.id;
        var oAC = new O2.Autocompleter(sSfx, null, '/query.svt'
           , { dispSN:'AGENCE_AUTOCOMPLETE', attrId: 111, minChars:4, indicator: 'indicator' + sSfx}); 
        $O2('autocpl'+sSfx)['autocompl'] = oAC;
        if (oAC.oInput.value) {
          oAC.switchToStaleMode();
          if (oAC.hdLibValue.value) oAC.choice.firstChild.textContent = oAC.hdLibValue.value;

        } else oAC.reset();
      }
    }
  }
  
  // on affiche la barre de boutons
  ODYForm.doShowButtonAlertDivForActionForm(oAlertDiv, p.sAction, p.iEntId, p.oCont, p.iMultiToRefresh, p.sFollowing
    , oParentFrm.id, p.sFollowingSecondStep);
  ODYForm.showAndCenterAlert(oParentFrm, oFondDiv, oAlertDiv);
}

// ---------- doShowButtonAlertDivForActionForm ----------------
/**
 * @param p_oAlertDiv div pour laquelle on souhaite afficher la div boutons
 * @param p_sAction action concernée par le formulaire d'action
 * @param p_iEntId id de l'entité principale
 * @param p_oCont container bouttons du form principal
 * @param p_iMultiToRefresh id de l'éventuel multi à réactualiser, s'il est présent, on fait un refresh multi en priorité suite à l'action  
 * sinon, on considère p_sFollowing
 * @param p_sFollowing suite à exécuter après avoir réalisé l'action p_sAction
 * @param p_sFollowingNotActionPre action à faire lorsque l'on clique sur le bouton valider de l'actionForm.
 * si non définie, par défaut est lancé le step 39 de edit.svt
 */
ODYForm.doShowButtonAlertDivForActionForm = function(p_oAlertDiv, p_sAction, p_iEntId, p_oCont, p_iMultiToRefresh
    , p_sFollowing, p_sFrame, p_sFollowingNotActionPre) {
  var oBtnDiv = p_oAlertDiv.findChildById("btnChoix","div");
  oBtnDiv.style.visibility = "";
  var oPbContinue = oBtnDiv.findChildById("continue","a");
  this.doShowAlertDivButtons(oBtnDiv);
  var sActionToDo = '';
  if (p_sFollowingNotActionPre) {
    sActionToDo = p_sFollowingNotActionPre;

  } else {
    var sFollowing = String(p_sFollowing||"") + ';hideErrors(null, '+p_oAlertDiv.id+' );alert("Opération terminée.")';
    sActionToDo = "doActionWithPre({sFrm:'"+p_sFrame+"',sAction:'" + p_sAction + "',iEntId:" + p_iEntId 
      + ",sSubmitData: sSubmitData"
      + ",sFollowingExpr:'" + (p_iMultiToRefresh 
                ? "refreshMulti($O2(\"" + p_oCont.id + "\")," + p_iMultiToRefresh + ")" 
                : sFollowing.escapeJS("'"))
      + "', bWithProgress:false, sContId:" + p_oAlertDiv.id + "})";
  }
  // id de l'éventuel multi à réactualiser
  var sAction = "ODYForm.onActionFormContinue(ev, '" + sActionToDo.escapeJS("'") + "')";
  var oNewFunction = new Function("e", "return navWithIndicator(this,'" + sAction.escapeJS("'") + "')");
  oPbContinue.listenTo("click", oNewFunction, false);
  oPbContinue.style.visibility = "";
}

//--------------  showAndCenterAlert ----------------
/* Affichage de la div Aletre au centre de la page sur fond gris
 * @param p_oParentFrm, p_oFondDiv, p_oAlertDiv, sContentDivId:
 */
ODYForm.showAndCenterAlert = function(p_oParentFrm, p_oFondDiv, p_oAlertDiv, p) {

  var oMsgDiv = p_oAlertDiv.findChildById(( (p && p.sContentDivId) || "msgContent") ,"div");
    
  // reinit les tailles
  p_oAlertDiv.style.height="";
  oMsgDiv.style.height="";  
    
  // affichage à faire AVANT sinon les "p_oAlertDiv.scrollHeight" et autres mesures sont à zéro
  p_oAlertDiv.show();
  p_oFondDiv.show();

  // si alerte ne tient pas en hauteur, déplacement ou scrollbar
  var iBottom = 0 + p_oAlertDiv.offsetTop + p_oAlertDiv.scrollHeight;
  var iMultiHeight = p_oParentFrm.clientHeight; 
  var iMultiScrollTop = p_oParentFrm.scrollTop; // pour que l'alerte se positionne au milieu de la partie VISIBLE en cas de scroll
  var oTitleDiv = p_oAlertDiv.findChildById("titre-alerte","div");
  var oBtnDiv = p_oAlertDiv.findChildById("btnChoix","div");
  var oStopBtn = oBtnDiv.findChildById("stop","a");
  if (iMultiHeight<iBottom) {
    if (iMultiHeight<p_oAlertDiv.clientHeight) {
      p_oAlertDiv.style.top="0px";
      if (iMultiHeight>450) { // si grande fenêtre, reduit taille de l'alerte
        p_oAlertDiv.style.height=(iMultiHeight-6)+"px";
        p_oAlertDiv.style.overflowY = "hidden";
        oMsgDiv.style.top = (oTitleDiv.offsetHeight-10) + "px";
        oMsgDiv.style.height =(iMultiHeight-oTitleDiv.offsetHeight-80)+"px";
        oBtnDiv.style.bottom = (iMultiHeight-10) + "px";
        oMsgDiv.style.overflowY = "auto";
      
      } else {
        p_oParentFrm.style.overflowY = "auto";
      }
    }
  }

  oMsgDiv.show();
  p_oAlertDiv.style.top = (iMultiScrollTop+(iMultiHeight-p_oAlertDiv.scrollHeight)/2) + "px";
}

// ---------- checkUnsavedModif ----------------
/*
 * vérfie qu'un formulaire avec des modifications non-enregistrées n'est pas ouvert
 */
ODYForm.checkUnsavedModif = function() {
  var oOFR = ODYFormRegistry.getInstance();
  var oModifedOF;
  oOFR.m_hForm.values().each(function (oOF) {
      // pas de controle si le formulaire n'est plus affiché ou est un formulaire de recherche
      if (!oOF.isValid() || oOF.isSearchForm() || !oOF.getMainContainer().visible()) return;
      if (oOF.countModifiedFields()==0) return;
      oModifedOF = oOF;
      throw $break;
    }
  );
  if (oModifedOF) {
    var oError = new OInputError();
    oError.m_sMsg = "Des modifications n'ont pas été enregistrées.<br>"
      + "Quittez d'abord le formulaire avec l'un des boutons 'Retour' ou 'Enregistrer'.<br><br>";
    oError.m_sLevel = "ERROR";
    ODYForm.doShowErrors([oError], oModifedOF.getContainer());
    return false;
  }
  return true;
}

//************************* INSTANCE ********************************

ODYForm.prototype.re = new Array();
ODYForm.prototype.re[3] = /^(\d{1,2})\/(\d{1,2})\/(\d{2,4})$/;  // date
ODYForm.prototype.re[4] = /^(\d{1,2})(\:(\d{1,2}))?(\:(\d{1,2}))?$/;          // time
ODYForm.prototype.re[8] = /^[\w-\.]+\@[\w\.-]+\.[a-z]{2,4}$/;   // email
ODYForm.prototype.re_alpha = /^[a-zA-Z\.\-]*$/;
ODYForm.prototype.re_alphanum = /^\w+$/;
ODYForm.prototype.re_unsigned = /^\d+$/;
ODYForm.prototype.re[2] = /^[\+\-]?[\d\s]*$/;         // integer
ODYForm.prototype.re[5] = /^([A-Z]{1,2}[1-9]{1,2}( w)*)|([\dAB]{4,5})$/; // CP, (ex: SW12 8UJ) ou 5 chiffres ou 4 chiffres pour belgique
ODYForm.prototype.re[6] = /^[\+\-]?[\d\s]*\.?\d*$/;   // amount
ODYForm.prototype.re[15] = ODYForm.prototype.re[6]; // real
ODYForm.prototype.re[7] = /^\d{2}(\d|[A-Z])\d{5}$/;              // NUMCLI
ODYForm.prototype.re[9] = /^[\d\s]+$/;            // phone
ODYForm.prototype.re[12] = /^\d{2}([0-9]|[A-Z]){4,5}$/; // NUMEMPL
ODYForm.prototype.re[17] = /^\d+$/; // numero de tract ^(00|[1-4][1-9])([0-9]{6})$

// ---------- initDOMForm ----------------
/**
 * @param p_iTS  timestamp du container
 * @param p_oCont container à considérer dans lequel sera cherché le formulaire (par défaut : oCC si pas de timestamp fourni)
 */
ODYForm.prototype.initDOMForm = function(p_sName, p_iTS, p_oCont) {
  // container dans lequel le formulaire <form> est cherché, oCC par défaut (mais attention, pb si reload car oCC a changé)
  var oCont = p_oCont;
  if (!oCont) oCont = p_iTS ? ODYCont.findContainerByTs(""+p_iTS) : oCC;
  this.m_oDOMForm = oCont.findChildById(p_sName, "form"); // le form : unique si recherche, celui de la frame but si edit
  if (!this.m_oDOMForm) throw "DOM object form '" + p_sName + "' not found in window '" + oCont.id + "' !";
  this.m_oDOMForm[ODYForm.sET_SN] = this;
  oCont[ODYForm.sET_SN] = this;
  this.m_moFields = this.m_oDOMForm.getElements();

  // tri les fields selon tabindex
  this.m_moFields.sort( function(a,b){  return a.tabIndex > b.tabIndex ? 1 : -1; } );
}

// ---------- setStepFrame ----------------
ODYForm.prototype.setStepFrame = function(p_iStep, p_sFrm) {
  this.m_iStep = p_iStep;
  this.m_sTargetFrm = p_sFrm;
}

// ---------- addTab ----------------
ODYForm.prototype.addTab = function(p_iTabId, p_sTabLib) {
  if (null==this.m_moTab) this.m_moTab = new Array();
  var oOFT = null;
  for (var i=0, l=this.m_moTab.length; i<l; i++) { // il peut y avoir ré-appel de addTab si reload de la frame "but"
    if (this.m_moTab[i].m_iId==p_iTabId) oOFT = this.m_moTab[i];
  }
  if (null==oOFT) {
    oOFT = new ODYFormTab(p_iTabId, p_sTabLib);
    this.m_moTab.push(oOFT);
    oOFT.m_iIndex = this.m_moTab.length;
  }
  oOFT.m_oParentODYForm = this;
}

//------------- registerSubForm ---------
ODYForm.prototype.registerSubForm = function(p_iTabId, p_sTS) {
  // récupération de l'onglet
  var oOFT      = this.getTabById(p_iTabId);
  var oMainCont = this.getMainContainer();
  // initialisation du DOM du formulaire HTML
  oOFT.m_oDOMForm = oMainCont.findChildById(sTAB_FORM_NAME_PREFIX + p_iTabId, "form");
  if (!oOFT.m_oDOMForm) throw "SubForm '" + (sTAB_FORM_NAME_PREFIX + p_iTabId) + "' not found."
    + "Current are :" + oOFT.toString();
  oOFT.m_oCont = oOFT.m_oDOMForm.getContainer();
  oOFT.m_oCont[ODYForm.sET_SN] = this;
  oOFT.m_oDOMForm[ODYForm.sET_SN] = this;
  return oOFT;
}

// ---------- getTabById ----------------
ODYForm.prototype.getTabById = function(p_iTabId) {
  if (!this.m_moTab) return;
  for (var i=0, l=this.m_moTab.length; i<l; i++) {
    if (this.m_moTab[i].m_iId==p_iTabId) return this.m_moTab[i];
  }
  return;
}

//------------- getEntityId ------------------
ODYForm.prototype.getEntityId = function() {
  var oForm = this.m_oDOMForm;
  if (!oForm["hdent.id"]) return;
  return oForm["hdent.id"].value;
}
//------------- setEntityId ------------------
ODYForm.prototype.setEntityId = function(p_iId) {
  var oForm = this.m_oDOMForm;
  if (!oForm["hdent.id"]) return;
  oForm["hdent.id"].value = p_iId;
}

//------------- setRunningResponse ------------------
ODYForm.prototype.setRunningResponse = function(p_oRep) { this.m_oRunningResp = p_oRep; }
ODYForm.prototype.getRunningResponse = function() { return this.m_oRunningResp; }

//------------- getEntityEditionId ------------------
ODYForm.prototype.getEntityEditionId = function() {
  var oForm = this.m_oDOMForm;
  if (!oForm["hdent.eid"]) return;
  return oForm["hdent.eid"].value;
}

//------------- isEntityNew ------------------
// @return true si l'entité n'a pas été sérialisée
ODYForm.prototype.isEntityNew = function() {
  return this.getEntityId()=="";
}

//------------- getDisplayMode ------------------
ODYForm.prototype.getDisplayMode = function() {
  var oIDM = this.m_oDOMForm["hddisp.mode"];
  return oIDM ? oIDM.value : null;
}

//------------- getDisplayId ------------------
ODYForm.prototype.getDisplayId = function() {
  var oForm = this.m_oDOMForm;
  return oForm["hddisp.id"].value;
}

//------------- getFormAction ------------------
ODYForm.prototype.getFormAction = function() {
  var sUrl = this.m_oDOMForm.elements["action"]
    ? this.m_oDOMForm.elements["action"].value
    : this.m_oDOMForm.action;
  return sUrl;
}

//------------- isSearchForm ------------------
ODYForm.prototype.isSearchForm = function() {
  var sMode = this.getDisplayMode();
  if (sMode) return (sMode.indexOf("RECH_")==0);
  return (!this.m_moTab);
}

//------------- isValid ------------------
/** @return true si le formulaire est toujours dans le tree HTML (ou au moins l'un des formulaires d'un des onglets si edition)*/
ODYForm.prototype.isValid = function() {
  var oForm = this.m_oDOMForm;
  if (oForm && oForm.isValid()) return true;
  if (!this.m_moTab) return false;
  for (var i=0, l=this.m_moTab.length; i<l; i++) {
    var oOFT = this.m_moTab[i];
    if (oOFT.m_oDOMForm && oOFT.m_oDOMForm.isValid()) return true;
  }
  return false;
}

// ---------- addError ----------------
/* Ajoute une erreur */
ODYForm.prototype.addError = function(p_oVisibleFrameElem, p_sCurValue, p_sCurText, p_sTabName
    , p_sFieldLabel, p_sMsg, p_sLevel) {
  if (!this.moErrorList) this.moErrorList = new Array();
  var oError = new OInputError(p_oVisibleFrameElem);
  oError.m_sValue = p_sCurValue;
  oError.m_sText = p_sCurText;
  oError.m_sTabName = p_sTabName;
  oError.m_sFieldName = p_sFieldLabel;
  oError.m_sMsg = p_sMsg;
  oError.m_sLevel = p_sLevel;
  this.moErrorList.push(oError);
}

// ---------- getInputByDisplayItemId ----------------
ODYForm.prototype.getInputByDisplayItemId = function(p_iDispiId, p_iAttrId, p_iEntiId) {
  if (this.isSearchForm()) {
    if (null==this.m_oDOMForm) return;
    var oInput = Form.getElements(this.m_oDOMForm).find( function(element) {
        return element.getDisplayItemId() == p_iDispiId;
      });
      if (!oInput) return;
    return oInput;
  }
  if (this.m_moTab) {
    for (var i=0, l=this.m_moTab.length; i<l; i++) {
      var oOFT = this.m_moTab[i];
      var oInput = oOFT.getInputByDisplayItemId(p_iDispiId);
      if (oInput) return oInput;
    }

  } else {
    oInput = this.m_oDOMForm[sFORM_INPUT_PREFIX + p_iAttrId + "_" + p_iEntiId];
    if (!oInput) oInput = this.m_oDOMForm[sFORM_INPUT_PREFIX + p_iAttrId];
    return oInput;
  }
}

// ---------- getInputByAttributeSn ----------------
ODYForm.prototype.getInputByAttributeSn = function(p_sAttrSn) {
  if (this.isSearchForm()) {
    if (null==this.m_oDOMForm) return;
    var oInput = Form.getElements(this.m_oDOMForm).find( function(element) {
        return (element.getAttributeName() == p_sAttrSn);
      });
      if (!oInput) return;
    return oInput;
  }
  for (var i=0, l=this.m_moTab.length; i<l; i++) {
    var oOFT = this.m_moTab[i];
    var oInput = oOFT.getInputByAttributeSn(p_sAttrSn);
    if (oInput) return oInput;
  }
}

// ---------- addParam ----------------
ODYForm.prototype.addParam = function(p_sParam, p_sValue, p_bNoOverwrite) {
  if (null==this.m_hAdditionalParam) this.m_hAdditionalParam = new Object();
  if (p_bNoOverwrite && this.m_hAdditionalParam[p_sParam]) throw "param '" + p_sParam +"' already exists with value '" 
    + this.m_hAdditionalParam[p_sParam] + "'";
  this.m_hAdditionalParam[p_sParam] = p_sValue;
}

// ---------- getContainer ----------------
/** @return le container qui contient directement le form : edib si rech, but si edition */
ODYForm.prototype.getContainer = function() { 
  return this.m_oDOMForm ? this.m_oDOMForm.getContainer() : null;
}
// ---------- getMainContainer ----------------
/** @return le container principal qui regroupe les onglets, les sous-forms et la zone des boutons :  : "edi"*/
ODYForm.prototype.getMainContainer = function() {
  var oCont = this.getContainer();
  return oCont ?  oCont.getContainer() : null;
}

// ---------- getButtonFrame ----------------
/** @return le container qui contient les boutons */
ODYForm.prototype.getButtonFrame = function() {
  var oButCont = this.getMainContainer().findChildContainerByPattern(".*but.*")[0];
  return oButCont;
}

// ---------- loadTab ----------------
/*
 * Charge le contenu de l'onglet (=de la div) si pas déjà chargé
 * @param p_iTabId  l'id de l'onglet (=<id display_tab>)
 * @return true si l'onglet a été, ou était chargé. false si chargement impossible
 */
ODYForm.prototype.loadTab = function(p_iTabId) {
  var oOFT = this.getTabById(p_iTabId);
  if (!oOFT) throw "Tab with id '" + p_iTabId + "' not found";
  return oOFT.load();
}

// ---------- moveFocus ----------------
/** déplace le curseur dans la direction indiquée par le keyCode de l'événement
 * @p_oCurentInput l'input courant avant le déplacement
 * @p_iRowJump     l'interval de tabIndex entre 2 lignes du form
 * @e  l'événement
 * @return le nvel input qui a le focus
 */
ODYForm.prototype.moveFocus = function(p_oCurentInput, p_iRowJump, e) {
  var iCurIdx = this.m_moFields.indexOf(p_oCurentInput);
  var iCurTabIdx = p_oCurentInput.tabIndex;
  var iBaseTabIdx, ii=0; 
  while(!iBaseTabIdx) { iBaseTabIdx = this.m_moFields[ii].tabIndex; ii++;}
  iBaseTabIdx = iBaseTabIdx - (iBaseTabIdx % p_iRowJump);
  var iMaxTabIdx = this.m_moFields[this.m_moFields.length-1].tabIndex;
  var oNewInput ;
  switch(e.keyCode) {
    case 37 :  // flèche gauche
      do {
        iCurIdx = iCurIdx>0 ? iCurIdx - 1 : this.m_moFields.length-1;
        oNewInput = this.m_moFields[iCurIdx];
      } while (oNewInput.type=="hidden" || oNewInput.readOnly);
      break;
    case 38 :  // flèche haut
      if (iCurTabIdx<(iBaseTabIdx+p_iRowJump)) {
        ; // rien
//        var iLastRowTabIdx =  iMaxTabIdx - (iMaxTabIdx % p_iRowJump);
//        iCurTabIdx = iLastRowTabIdx + (iCurTabIdx % p_iRowJump); 
      } else iCurTabIdx -= p_iRowJump;
      oNewInput = this.m_moFields.find(function(p) { return (p.tabIndex==iCurTabIdx)});
      break;
    case 39 :  // flèche droite
      do {
        iCurIdx = iCurIdx<this.m_moFields.length-1 ? iCurIdx + 1 : 0;
        oNewInput = this.m_moFields[iCurIdx];
      } while (oNewInput.type=="hidden" || oNewInput.readOnly);
      break;
    case 40 :  // flèche bas
      iCurTabIdx += p_iRowJump;
      if (iCurTabIdx>iMaxTabIdx) ; // rien.   iCurTabIdx = iBaseTabIdx + iCurTabIdx % p_iRowJump;
      oNewInput = this.m_moFields.find(function(p) { return (p.tabIndex==iCurTabIdx)});
      break;
    default: return true;
  }
  if (oNewInput) oNewInput.focus();
  return false;
}

//------------- toString ------------------
ODYForm.prototype.toString = function() {
  var s = this.m_sName + " - form[" + (this.m_oDOMForm?this.m_oDOMForm.id:"-") +"]";
  var sTabs = "\n   ";
  if (!this.isSearchForm()) {
    sTabs += "Tabs=[";
    this.m_moTab.each(function(value, index){ sTabs += value.m_sLib+","});
    sTabs = sTabs.substring(0,sTabs.length-1)+"]";

  } else sTabs += "<no tabs>";

  s += sTabs
    + "\n   m_msDoublonFields=" + this.m_msDoublonFields;
  return s;
}

//--------------  validate ----------------
/**
 * vérification des champs obligatoires et des formats. met à jour la liste this.moErrorList.
 */
ODYForm.prototype.validate = function() {
  this.moErrorList = new Array();
  this.moFileList = new Array();
  var oForm = this.m_oDOMForm;
  for (var j=0, l=oForm.elements.length ; j < l ; j++) {
    var elem = oForm.elements[j];
    if (null==elem) continue;
    var sValue = null==elem.value ? null : elem.value.trim();
    var sName = elem.name;
    var sDataItemTypeSn = elem.getAttribute("dataItemTypeSn");
    if (!sName || (sName.indexOf(sFORM_INPUT_PREFIX)!=0 && sName.indexOf(sPARAMFORM_INPUT_PREFIX)!=0) ) continue;
    { // si demande de suppression pour cette entité (table_form) => pas de controle
      var ms = sName.split("_");
      if (ms.length>2) var sEntityEdId = ms[2];
      var oDelChk = oForm.elements[sFORM_DEL_FLAG_PREFIX + sEntityEdId];
      var bDeletion = (oDelChk && oDelChk.checked);
      if (bDeletion) continue;
      if ('editing_area'==sDataItemTypeSn) {
        var oEditor = FCKeditorAPI.GetInstance(sName);
        sValue = oEditor.GetXHTML(true);
        //mise à jour du champ caché
        elem.value = sValue;
      } else if ('select_checkable'==sDataItemTypeSn) {
        sValue = getCurrentValueMSC(elem.id, ';');
      }
    }
    var sMsg = null;
    var sId=elem.id;
    // test champ obligatoire
    if (""==sValue && sId.substr(sId.length-1,1)!="_") {  //On exclut les champs modèles
      if (elem.getAttribute("mandatory")=="true") {
        sMsg = "Ce champ est obligatoire";
        sValue = ""; // pour affichage de l'input dans le msg d'erreur
      }

    } else {
      var iDataTypeId = elem.getAttribute("dataTypeId");
      //dans le cas d'un type de format binaire ou file, difficile de testser le contenu!
      //il va falloir faire une soumission directe du formulaire : moFileList va pvr le permettre dans sendStage2
      if ('upload'==sDataItemTypeSn) {
        this.moFileList.push(elem.id);
      } else {
      var re = ODYForm.prototype.re[iDataTypeId];
      // éventuel sélecteur du type de recherche dans un form de rech ("commence par", "contient", ...) 
      var oOptInput = oForm["opt_"+sName];
      // pas de contrôle de la valeur si le type de recherche existe et n'est pas "exactement"
      if ((!oOptInput || "ex"==oOptInput.value) && re) {
        if (!re.test(sValue)) {
          sMsg = "Le format de ce champ est invalide";

        } else if (3==iDataTypeId) { // date
          if (RegExp.$1 > 31 || RegExp.$2 > 12) sMsg = "Le format de date est invalide";
          // check number of day in month
          var dt_test = new Date(RegExp.$3, Number(RegExp.$2-1), RegExp.$1);
          if (dt_test.getMonth() != Number(RegExp.$2-1)) sMsg = "Date incohérente : ce jour n'existe pas dans ce mois";
  
        } else if (4==iDataTypeId) { // time
          // check allowed ranges
          if (RegExp.$1 > 23 || RegExp.$2 > 59 || RegExp.$3 > 59) sMsg = "Le format de l'heure est invalide";
        }
      } 
    }
    }
    if (sMsg) {
      this.addError(elem.visible() ? elem : null, sValue, sValue, elem.getAttribute("tabLabel"), elem.getAttribute("dispiLabel"), sMsg);
   //   addCssStyle(elem, "hasError");
    }
  }
}

//--------------  isOneStage ----------------
ODYForm.prototype.isOneStage = function() {
  return bFORCE_ONE_STAGE || this.isSearchForm() || this.m_iStep==5 || this.m_oDOMForm.id.indexOf("param")==0;
}

//--------------  validateAndSubmit ----------------
/**
 * @return la réponse du serveur si le formulaire est soumis, false sinon
 */
ODYForm.prototype.validateAndSubmit = function(p_oMainDiv, p_sFollowing, p_bNoDisplay) {
  var iNow = new Date().getTime();
  if (this.lastSubmitTime) { // pour éviter les doubles soummissions
    if ((iNow-this.lastSubmitTime)<1000) {
      if (bAdmin) alert("En cours de traitement...");
      return false;
    }
  }
  // protection en cas d'erreur sur un lien : submit bloqué si readonly, sauf si step 4 utilisé explicitement pour contourner
  if (this.m_bReadOnly && this.m_iStep!=4) {
    alert("Cette fiche ne peut être enregistrée. Merci de contacter l'équipe technique pour décrire le problème.");
    return false;
  }
  this.lastSubmitTime = iNow;
  var sTarget = this.findTarget();

  this.validate();
  if (this.moErrorList.length>0) {
    this.doShowErrors();
    return false;
  }

  var bNoDisplay = p_bNoDisplay;
  // force le "noDisplay" : on fait l'update en background, sauf pour module report
  if (this.m_oDOMForm.id!="choix" && this.m_oDOMForm.id!="cvtheque") bNoDisplay = true;

  var oTargetContainer = findContainerById(sTarget); // la target "théorique"
  var bInDiv = sTarget.indexOf("js")==0 || (null!=oTargetContainer && oTargetContainer.tagName=="DIV");
  if (bInDiv || bNoDisplay) {
    if (sTarget.match(".*parent")) sTarget = oTargetContainer.id;
    
    if (oTargetContainer) {
      var sWaitingMsg = this.isSearchForm() ? "Recherche en cours ..." : "Enregistrement en cours ...";
      ODYForm.doShowWaiting(oTargetContainer, sWaitingMsg);
    }

    var sUrl = this.getFormAction();
    var sSubmit = formData2QueryString(this.m_oDOMForm);
    // pour éviter double valorisation du param "frm"
    if (sTarget) sSubmit = sSubmit.replace(/(^|&)frm=.*?(&|$)/,"$1");
    if (this.m_hAdditionalParam) {
      for (var sParam in this.m_hAdditionalParam) sSubmit += (sSubmit!=''?'&':'') + sParam + '=' + escape(this.m_hAdditionalParam[sParam]);
    }
    if (bNoDisplay) sTarget = "js:"+sTarget;

    if (this.isOneStage()) {
      if (this.m_iStep3) { // si step 3 indiqué, c'est qu'il y a soumission en 2 temps : verif , puis affichage en m_iStep3
        var sFollowing = "'js:navigate(\\'"+sUrl+"\\', " + this.m_iStep3 + ", \\'" + this.findTarget() + "\\')'";
        var sSuccessExpr = "sendForm2('" + this.m_sName + "',oRep," + sFollowing + "," + p_bNoDisplay + ")";
        var oResult = navigateIfNoError(sUrl, this.m_iStep, sTarget, sSubmit, sSuccessExpr, this);

      // sinon tout est fait en une seule fois
      } else var oResult = navigate(sUrl, this.m_iStep, sTarget, sSubmit, bNoDisplay);

    } else { // saveEntity en deux étapes (step 300 et 301)
      var sFollowing = p_sFollowing
        ? "'" + p_sFollowing.replace(/'/g,"\\'") + "'"
        : p_oMainDiv.id.match("ifa_.*") 
          ? "'js:exitMultiFicheFromId("+p_oMainDiv.id+")'" 
          : (this.m_iStep3
            ? "'navigate(\\'"+sUrl+"\\', " + this.m_iStep3 + ", \\'" + this.findTarget() + "\\')'"
            : "null");
      // expression pour l'exécution du step 2 : le navigateIfNoError va retourner false si erreur, le résultat
      // du navigate sinon (XML)
      var sResultExpr = "navigateIfNoError('"+sUrl+"', " + this.m_iStep2 + ", '" + sTarget + "','" + sSubmit 
        + "', null, ODYFormRegistry.getODYFormByFormName('" + this.m_sName + "'))";
      var sSuccessExpr = "oRep = sendForm2('" + this.m_sName + "'," + sResultExpr + "," + sFollowing + ","
        + p_bNoDisplay + ")";
      var oResult = navigateIfNoError(sUrl, this.m_iStep, sTarget, sSubmit, sSuccessExpr, this);
    }
    // si les navigate renvoient des erreur il faut masque la fenetre Waiting
    if (oTargetContainer) {      
      ODYForm.doHideWaiting(oTargetContainer);
    }
    return oResult;

  } else { // if (bInDiv || bNoDisplay)
    // si des infos de nav sont présentes, mise à jour des hidden du formulaire avant soumission
    if (this.m_iStep && this.m_iStep>=0) this.m_oDOMForm.elements["step"].value = this.m_iStep;
    if (sTarget) this.m_oDOMForm.elements["frm"].value = sTarget;
    if (this.m_sFollowing) this.m_oDOMForm.elements["hdfollowing"].value = this.m_sFollowing;
    this.m_oDOMForm.submit();
    return true;
  }
  return false;
}

//------------------ findTarget ------------------
ODYForm.prototype.findTarget = function() {
  var sTarget = this.m_sTargetFrm;
  // lecture de l'input "frm" du formulaire HTML
  if (!sTarget) sTarget = this.m_oDOMForm.elements["frm"].value;

  // si pas de target indiquée, utilisation de l'attribut "target" du form html
  if ((!sTarget || ""==sTarget) && this.m_oDOMForm.target) sTarget = this.m_oDOMForm.target;

  // substitution immédiate d'un éventuel "_parent" par l'id réel
  if (sTarget) {
    var oTgt = decodeFrameDesig(findFunctionCallerObject(), sTarget);
    if (oTgt && oTgt.id) sTarget = oTgt.id;
  }
  this.m_sTargetFrm = sTarget;
  return sTarget;
}

//------------------ onSendAction ------------------
function onSendAction(p_oMainFormElem, p_oFrameElem) {
  var sValue = getCurrentValue(p_oFrameElem);
  // copie de la valeur dans le formulaire de la frame "button"
  p_oMainFormElem.value = sValue; 
}

//------------------ send ------------------
/**
 * Soumet les données du formulaire pour mise à jour de l'entité et vérif des données
 *
 * @return true si le formulaire a été envoyé sans erreur
 */
ODYForm.prototype.send = function(p_iStep, p_sFrm, p_sFollowing, p_bNoDisplay) {
  if (!this.isSearchForm()) { // cas d'une édition
    var sMsg = this.checkMulti();
    if (sMsg) {
      alert(sMsg);
      return false;
    }
    // parcours des frames chargées pour recopie des valeurs dans le form caché de la frame "but"
    var iProcessed = this.scanForm(onSendAction, "alert('Le chargement n\'est pas terminé. Merci de patienter.')");
    if (-1==iProcessed) return false;

  } else {
    var oForm = this.m_oDOMForm;
    // remplissage des hidden pour comparaison  // RSI 18/06/07 : a quoi ca sert ?
    for (j=0 ; j < oForm.elements.length;j++) {
      var elem = oForm.elements[j];
      if (null==elem) continue;
      var sType = elem.type;
      if(sType!="hidden" && sType!="checkbox") continue;
      if(elem.getAttribute) {
        if(sType=="hidden") {
          var sRef = elem.getAttribute("ref");
          if(sRef) {
            // alert("query : ref=" + sRef + " for input " + elem.id);
            elem.value = oForm.elements["n_"+sRef].value;
          }
        }
        var sOption = elem.getAttribute("option");
        if (sOption) { alert("query : sOption=" + sOption); elem.value = sOption; }
      }
    }
  }

  var oMainDiv = this.getMainContainer();
  // dans le cas d'un multi, ajout des param "mainEntId" et "attrId"
  if (oMainDiv.getAttribute(sATTR_MAIN_ENT_ID)) {
    this.addParam(sPARAM_MAIN_ENT_ID , oMainDiv.getAttribute(sATTR_MAIN_ENT_ID));
    this.addParam(sPARAM_ATTRIBUTE_ID, oMainDiv.getAttribute(sATTR_ATTRIBUTE_ID));
  }

  // si une frame est passée explicitement => forcage
  if (p_sFrm) this.m_sTargetFrm = p_sFrm;

  // appel systématique à findTarget() qui intialise m_sTargetFrm si ce n'était pas encore fait + decode
  this.findTarget();

  var iNaturalStep = this.m_oDOMForm.elements["step"].value;
  if (!this.m_iStep) this.m_iStep = iNaturalStep;
  if (p_iStep) this.m_iStep = p_iStep;
  if (!this.m_iStep) this.m_iStep = this.m_iStep1;

  var bNoDisplay = p_bNoDisplay;
  // force le "noDisplay" : on fait l'update en background, sauf pour module report
  if (this.m_oDOMForm.id!="choix" && this.m_oDOMForm.id!="cvtheque") bNoDisplay = true;

  var oResult = this.validateAndSubmit(oMainDiv, p_sFollowing, p_bNoDisplay);
  // si le formulaire n'a pas été soumis car erreurs détectées au niveau des contrôles js, exit
  if (false==oResult) return;

  if (this.isOneStage()) return this.sendStage2(oResult, p_sFollowing, p_bNoDisplay);
  
  // tous s'est bien passé : si frame toujours visible, cacher la div d'alerte
  var sTarget = this.findTarget();
  var oTargetContainer = findContainerById(sTarget); // la target "théorique"
  if (oTargetContainer) ODYForm.doHideWaiting(oTargetContainer);
  
  return oResult; // important car si pas de retour les tests du type "if(sendForm())" ne passent pas
}

//------------------ sendStage2 ------------------
ODYForm.prototype.sendStage2 = function(p_oResult, p_sFollowing, p_bNoDisplay) {

  if (p_oResult==false) return false;  // retour possible de navigateIfNoError

  var bNoDisplay = p_bNoDisplay;
  // force le "noDisplay" : on fait l'update en background, sauf pour module report
  if (this.m_oDOMForm.id!="choix" && this.m_oDOMForm.id!="cvtheque") { 
    bNoDisplay = true;
  }

  var oTargetContainer = findContainerById(this.findTarget());
  if (oTargetContainer) {
    var sWaitingMsg = this.isSearchForm() ? "Recherche en cours ..." : "Enregistrement en cours ...";
    ODYForm.doShowWaiting(oTargetContainer, sWaitingMsg);
  }

  // tentative de conversion du resultat en xml
  var xResult;
  try {
    if (typeof(p_oResult)=="string") xResult = getDocElement(p_oResult);
    else if (getClassName(p_oResult).indexOf("DOMElement")!=-1) xResult = p_oResult;
  } catch(e) {}

  // si erreurs
  var bHasError = false;
  if (xResult) {
    var vErrList = xResult.selectNodeSet("//errorl/formError");
    if (!vErrList || vErrList.length==0) vErrList = xResult.selectNodeSet("//data/error");
    var bHasError = (vErrList.length>0);
    var vMsgList = xResult.selectNodeSet("//errorl/formError");
    var bHasMsg = bHasError || (vMsgList.length>0); 
  }
  
  if (bHasError) {
    var oMainDiv = this.getMainContainer();
    // dans le cas d'un multi, si des messages info existent, seul le bouton "Continuer" sera disponible
    // et provoquera la sortie de la fiche d'édition du multi
    if(oMainDiv.id.match("ifa_.*")) this.showErrors(p_oResult, "exitMultiFicheFromId(" + oMainDiv.id + ")"); 
    else this.showErrors(p_oResult);
    return false;
  } else if (this.moFileList && this.moFileList.length>0) {
      //il faut soumettre les vrais formulaires!
    for(var k=0, l=this.m_moTab.length ; k<l ; k++) {
      //enregistrement de fichiers en BD
      moTab = this.m_moTab[k];
      var vFile = Form.getInputs(moTab.m_oDOMForm.id,"file");
      if (!vFile || vFile.length==0) continue;
      moTab.m_oDOMForm.encoding = "multipart/form-data";
      moTab.m_oDOMForm.target = "uplaodResult";//nom de l'iframe
      moTab.m_oDOMForm.elements["step"].value = 26;//on force l'action spécifiq de l'upload
      moTab.m_oDOMForm.submit();
    }
  }
  
  if (xResult) {
    var oDivMsg = $O2(sCONFIRM_MSG_DIV);
    // si enregistrement réussi, affiche message pendant 10s
    try {
      var iEntId;
      var iQRId;
      if (this.isSearchForm()) {
        var xQR = xResult.selectNodeSet("//data/qr");
        if (xQR) xQR = xQR.item(0);
        if (xQR) iQRId = xQR.getAttribute("histoTime");

      } else {
        var xEnt = xResult.selectNodeSet("//data/e").item(0);
        iEntId = xEnt.getAttribute("id");
        var iEntETId = xEnt.getAttribute("type_id");
        ODYNavigation.setIdOfLastEntity(iEntId, iEntETId);
        if (oDivMsg) var sEntityName = xEnt.selectNodeSet("name").item(0).getFirstChild().getNodeValue();
      }

    } catch (e) {}
    // msg de confirmation ssi enregistrement EN BASE, or si pas d'id l'entité n'a pas été sérialisée => pas de msg
    // (ex: modif d'une entitée liée d'une entité clonée)
    if (oDivMsg && iEntId && ""!=iEntId) {
      oDivMsg.innerHTML = (sEntityName ? sEntityName + " : " : "") + "Enregistrement OK";
      oDivMsg.show('inline');
      setTimeout(function() {$O2(sCONFIRM_MSG_DIV).hide()}, 10000);
    }
  }

  // si l'affichage a été fait, exit
  if (!bNoDisplay) return true;

  if (p_bNoDisplay) { // si pas d'affichage demandé (donc formulaire reste chargé), maj des champs
    this.locallyCommit(iEntId, iEntETId);
  }

  // sinon poursuit la navigation
  if (p_sFollowing) {
    if (p_sFollowing.indexOf("js:")==0) eval(p_sFollowing.substring(3));
    else navigate(p_sFollowing, null, null, null, p_bNoDisplay);

  } else if (!p_bNoDisplay) { // si la demande initiale n'était pas "noDisplay", enchainement par défaut
    // sur l'affichage de la liste de résultats si recherche
    if (this.isSearchForm()) navigate(addUrlParam("/query.svt",sPARAM_QRESULT_ID,iQRId), 10, this.m_sTargetFrm);
    // retour sur le context précédent pour une édition
    else ODYNavigation.contextBack(true /* p_bIsSaveAction */);

  } else {//on cache la div Waiting
    var sTarget = this.findTarget();
    var oTargetContainer = findContainerById(sTarget); // la target "théorique"
    ODYForm.doHideWaiting(oTargetContainer);
  }
  return p_oResult;
}

//--------------  showErrors ----------------
/**
 * liste les champs en erreur
 * @param p_oXmlDoc le document XML qui contient les erreurs
 * @param p_sFollowingExpr
 */
ODYForm.prototype.showErrors = function(p_oXmlDoc, p_sFollowingExpr) {
  this.moErrorList = ODYForm.buildErrorsArray(p_oXmlDoc, this);
  this.doShowErrors(p_sFollowingExpr);
}

//--------------  doShowErrors ----------------
/**
 * @param p_sFollowingExpr
 */
ODYForm.prototype.doShowErrors = function(p_sFollowingExpr) {
  return ODYForm.doShowErrors(this.moErrorList, this.getContainer(), p_sFollowingExpr);
}
ODYForm.doShowWaitingAction = function (p_oTargetContainer){
 var sMsg = 'Traitement en cours';
 return ODYForm.doShowWaiting(p_oTargetContainer,sMsg);
}
//--------------  doShowMessageAgency ----------------
/**
 * p.oTargetContainer : le container dans lequel chercher / créer l'alertDiv
 * p.sHTML            : le HTML du message à afficher
 * p.xDataNode        : le DOM qui contient le message (alternative à p.sHTML)
 * p.sFollowingExpr   : l'expr à exécuter sur le "continuer" du message
 */
ODYForm.doShowMessageAgency = function (p) {
  var sMsg = p.sHTML;
  if (p.xDataNode) {
    var xValSet = p.xDataNode.selectNodeSet("avl/av[@asn='agce_message']/val");
    if (1==xValSet.length) sMsg = xValSet.item(0).getFirstChild().getNodeValue();
  }
  if (!sMsg || sMsg.length==0) return;
  
  var oParentFrm = this.getParentFrmForAlertDiv(p.oTargetContainer);
  var oAlertDiv = ODYForm.getAlertDiv(oParentFrm);
  
  // masque titre "enregistrement données" et affiche titre "Erreur"
  oAlertDiv.findChildById("titreErreur","div").style.display = "";
  oAlertDiv.findChildById("titreValidation","div").style.display = "none";
  
  // on affiche la barre de boutons
  var oBtnDiv = this.doShowAlertDivButtonsForErrors(null, oAlertDiv, p.sFollowingExpr);
  // cache le bouton "annuler" qui n'a pas de sens dans ce contexte
  var oPbStop = oBtnDiv.findChildById("stop","a");
  oBtnDiv.style.visibility = "hidden";
  oPbStop.style.visibility = "hidden";

  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.style.visibility = "";
  var oFondDiv = ODYForm.getFondGrisDiv(oParentFrm);
  var sPrevTab = "";
  oAlertDiv.findChildById("titre","div").innerHTML = "ATTENTION: message agence";
  oMsgDiv.innerHTML = sMsg;
  ODYForm.showAndCenterAlert(oParentFrm, oFondDiv, oAlertDiv);
  //il faut obligatoirement faire apparaitre l'image de la croix qui permet de fermer un message agence!
  oAlertDiv.findChildById("close","img").style.display = "";
}

//--------------  doShowEndAction ----------------
ODYForm.doShowEndAction = function (p_oTargetContainer,p_sHTML,p_sFollowingExpr){
  var oParentFrm = this.getParentFrmForAlertDiv(p_oTargetContainer);
  var oAlertDiv = ODYForm.getAlertDiv(oParentFrm);
  
  // masque titre "enregistrement données" et affiche titre "Erreur"
  oAlertDiv.findChildById("titreErreur","div").style.display = "";
  oAlertDiv.findChildById("titreValidation","div").style.display = "none";
  
  // on affiche la barre de boutons
  var oBtnDiv = this.doShowAlertDivButtonsForErrors(null,oAlertDiv, p_sFollowingExpr);
  // cache le bouton "annuler" qui n'a pas de sens dans ce contexte
  var oPbStop = oBtnDiv.findChildById("stop","a");
  oPbStop.style.visibility = "hidden";

  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.style.visibility = "";
  var oFondDiv = ODYForm.getFondGrisDiv(oParentFrm);
  var sPrevTab = "";
  oAlertDiv.findChildById("titre","div").innerHTML = "Résultat de traitement";
  oMsgDiv.innerHTML = p_sHTML;
  ODYForm.showAndCenterAlert(oParentFrm, oFondDiv, oAlertDiv);
}
//--------------  doShowWaiting ----------------
ODYForm.doShowWaiting = function(p_oTargetContainer, p_sMsg) {
  var oAlertParentFrm = p_oTargetContainer;
  var oAlertDiv = ODYForm.getAlertDiv(oAlertParentFrm);

  // masque titre "enregistrement données" et affiche titre "Erreur"
  oAlertDiv.findChildById("titreErreur","div").hide();
  oAlertDiv.findChildById("titreValidation","div").show();
  
  // on affiche la barre de boutons
  var oBtnDiv = oAlertDiv.findChildById("btnChoix","div");
  oBtnDiv.hide();

  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.innerHTML = p_sMsg;
  oMsgDiv.show();
  var oFondDiv = ODYForm.getFondGrisDiv(oAlertParentFrm);
  ODYForm.showAndCenterAlert(oAlertParentFrm, oFondDiv, oAlertDiv);
}

//--------------  doHideWaiting ----------------
ODYForm.doHideWaiting = function(p_oTargetContainer) {
  var oAlertParentFrm = p_oTargetContainer;
  var oAlertActionFormDiv = ODYForm.getAlertDiv(oAlertParentFrm, ODYForm.sACTION_FORM_DIV_ID);
  var oMsgActionFormDiv = oAlertActionFormDiv.findChildById("msgContent","div");
  var oAlertDiv = ODYForm.getAlertDiv(oAlertParentFrm);
  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  var oFondDiv = ODYForm.getFondGrisDiv(oAlertParentFrm);
  oMsgDiv.hide();
  oAlertDiv.hide();
  oMsgActionFormDiv.hide();
  oAlertActionFormDiv.hide();
  oFondDiv.hide();
}

//--------------  showActionForm ----------------
ODYForm.prototype.showActionForm = function(p) {
  p.oCont = this.getContainer();
  return ODYForm.doShowActionForm(p);
}

//--------------  doShowValidation ----------------
/**
 * @param p_sFollowingUrl   url de destination si pas de modif ou si l'utilisateur choisi de continuer
 * @param p_sFollowingExpr  expression javascript à exécuter si pas de modif ou si l'utilisateur choisi de continuer 
 *                (alternative à p_sFollowingUrl)
 * @param p_bInLine  si true, dans le cas d'un "Save and continue", la sauvegarde se fait sans rechargement du 
 *    formulaire
 * @param p_oClickedLink   l'objet cliqué à l'origine de l'action
 */
ODYForm.prototype.doShowValidation = function(p_sFollowingUrl, p_sFollowingExpr, p_bInLine, p_oClickedLink) {
  var oButFrm    = findContainer(this.m_oDOMForm);
  var oParentFrm = oButFrm.getContainer();
  var oTabsFrm   = findContainee(oParentFrm,".*tabs.*");
  var oFirstTab = findContainee(oParentFrm,".*frame(_\\d+)?_1(\\.\\d+)?$");
  var oAlertDiv = ODYForm.getAlertDiv(oParentFrm);
  var oFondDiv = ODYForm.getFondGrisDiv(oParentFrm);
  oAlertDiv.findChildById("titreErreur","div").style.display = "none";
  oAlertDiv.findChildById("titreValidation","div").show();
  var oBtnDiv = oAlertDiv.findChildById("btnChoix","div");
  oBtnDiv.style.display = "";
  oBtnDiv.style.visibility = "";
  var oMsgDiv = oAlertDiv.findChildById("msgContent","div");
  oMsgDiv.show();
  var sHTML = "Des modifications ont été apportées sur la fiche en cours :<br/><div id=\"msgListeModif\"><ul>";
  for (var i=0, l=this.m_msModified.length; i<l; i++) {
    sHTML += "<li>" + this.m_msModified[i];
  }
  oMsgDiv.innerHTML = sHTML + "</ul></div>";

  oBtnDiv.findChildById("continue","a").style.display = "none";
  oBtnDiv.findChildById("stop"    ,"a").style.display = "none";
  
  var oPbSave    = oBtnDiv.findChildById("saveAndContinue","a");
  var oPbDiscard = oBtnDiv.findChildById("discardAndContinue","a");
  var oPbBack    = oBtnDiv.findChildById("back","a");
  oPbSave.style.display = "";
  oPbSave.style.visibility = "";
  oPbDiscard.style.display = "";
  oPbDiscard.style.visibility = "";
  oPbBack.style.display = "";
  oPbBack.style.visibility = "";

  // ----------------------------------------
  // bouton "Save & continue"
  oPbSave.clearListeners();
  var sAction = "hideErrors(window.event||e,"+oAlertDiv.id+");";
  var sFollowing = (p_sFollowingExpr && p_sFollowingExpr!="") 
      ? "js:"+p_sFollowingExpr.replace(/return false/g,"") // NB : pas de return possible en "js:"
      : (p_bInLine 
          ? "navigate(\"" + p_sFollowingUrl + "\")"
          : p_sFollowingUrl
        );
  sAction += "sendForm("+this.m_iDisplayId+",null,null,\"" + sFollowing + "\", " + p_bInLine + ")";
  sAction += ";return false;";

  var bIsCancel = sAction.indexOf("cancel(")!=-1 || (p_oClickedLink && p_oClickedLink.id.match(/pbCancel.*/));
  // si clic sur le bouton "cancel", le bouton "continuer en enregistrant" est masqué
  if (bIsCancel) {
    oPbSave.style.visibility = "hidden";

  } else {
    oPbSave.listenTo("click", new Function("e", sAction), false);
    oPbSave.style.visibility = "";
  }

  // ----------------------------------------
  // bouton "Discard & continue"
  oPbDiscard.clearListeners();
  var sAction = "hideErrors((window.event||e),"+oAlertDiv.id+");" + (p_sFollowingExpr && p_sFollowingExpr!=""
      ? p_sFollowingExpr
      : "navigate(\"" + p_sFollowingUrl + "\"); return false;");
  if (!bIsCancel && !this.getEntityId()) { // si entité non-sérialisée, discard interdit : soit Save soit Back !
    oPbDiscard.style.visibility = "hidden";

  } else {
    oPbDiscard.listenTo("click", new Function("e", sAction), false);
    oPbDiscard.style.visibility = "";
  }
  
  // montre l'alerte
  ODYForm.showAndCenterAlert(oParentFrm, oFondDiv, oAlertDiv);
}

//--------------  scanForm ----------------
/**
 * Itère sur les frames qui composent la fiche. Pour chaque frame, itère sur les inputs de son formulaire et 
 * exécute pour chacun de ces inputs l'action désignée par p_sFuncName.
 * Si une frame n'est pas totalement chargée (bLoaded!=true), le scan est interrompu après exécution de la fonction
 * p_sOnNotLoaded.
 * Les inputs dont le nom commence par "lib" ou "pb" sont ignorés.
 * L'itération se fait sur this.m_moTab.
 *
 * @param p_sFuncName nom de la fonction a executer pour chacun des inputs parcourus : ex : listErrorField
    , sendFormAction, countModifiedFieldsAction
 * @param p_sOnNotLoaded expression js qui indique l'action a executer si tous les forumlaires ne sont pas encore chargés
 * @return le nombre d'input parcourus, -1 si l'une des frames n'est pas encore chargée
 */
ODYForm.prototype.scanForm = function(p_sFuncName, p_sOnNotLoaded) {
  var iProcessed = 0;
  var oMainForm = this.m_oDOMForm;
  // le conteneur de niveau supérieur : frame parente des tabs (genre "edi"), IFrame, ou div d'un multi
  var oMainContainer = this.getMainContainer();
  var sTS = oMainContainer["ts"];

  if (!this.m_moTab) return 0; // pour les table_form
  // parcours des onglets
  for (var i=0, tl=this.m_moTab.length ; i<tl ; i++) {
    // si le form n'est plus affiché, exit (suite à une navigation rapide par ex)
    if (!this.m_oDOMForm.isValid()) return 0;
    var oOFT = this.m_moTab[i];
    var oTabContainer = findContainee(oMainContainer, "^frame_"+(i+1)+"(\\."+sTS+")?$");
    if (!oTabContainer) {
      var iDispiId = oMainContainer.id.substring(4);  // on supprime "ifa_"
      oTabContainer = findContainee(oMainContainer,"^wizardm_frame_" + iDispiId + "_"+(i+1)+"(\\."+sTS+")?$");
    }
    if (!oTabContainer) {
      oTabContainer = findContainee(oMainContainer,"^fiche_frame_"+(i+1)+"(\\."+sTS+")?$");
    }
    if (!oTabContainer || !oTabContainer.isLoaded()) {
      if (oTabContainer && oTabContainer.isLoading()) {
        eval(p_sOnNotLoaded);
        return -1;
      } else continue; // le chargement n'a juste pas été demandé => on passe outre le tab
    }
    var oForm = oOFT.m_oDOMForm;
    // parcours des elements du formulaire
    for (var j=0, l=oForm.elements.length ; j < l ;j++) {
      var elem = oForm.elements[j];
      // passe les boutons, les libelles et les hidden
      if (elem.name.substring(0,2)=="pb" || elem.name.substring(0,3)=="lib" || elem.name.substring(0,2)=="hd") continue;
      // passe les radio buttons non sélectionnés
      if ((elem.type=="radio") && !elem.checked) continue;
      var mainElem = oMainForm.elements[elem.name];
      if (null==elem || null==mainElem) {
        if (sDbg) sDbg += "from elem '" + elem.name + "' : found in validating form : " + mainElem;
        continue;
      }
      if (p_sFuncName) {
        var oFunc = typeof(p_sFuncName)=="function" ? p_sFuncName : eval(p_sFuncName);
        oFunc.call(this, mainElem, elem, oOFT.m_sLib);
      }
      iProcessed++;
    }
  }
  return iProcessed;
}

//--------------  checkModified ----------------
/**
 * Analyse les champs du formulaire pour détecter les éventuelles modifications.
 * Avant toute chose, détection d'éventuels multi ouverts => dans ce cas return avec message bloquant.
 *
 * @return true si l'action doit continuer, false sinon
 */
ODYForm.prototype.checkModified = function(e, p_oClickedButton, p_sFollowingUrl, p_sFollowingExpr, p_bInLine) {

  var sMsg = this.checkMulti();
  if (sMsg) {
    alert(sMsg);
    return false;
  }

  var iModified = this.countModifiedFields();

  // si ajout dans un multi, que l'entité parente est new et qu'in l'y a eu aucune modif (== il n'y aura pas de
  // demande de confirmation d'enregistrement), insère une pseudo modif pour demander l'enregistrement
  var bCheckIsNew = (p_oClickedButton && p_oClickedButton.id && p_oClickedButton.parentNode.id.indexOf("wiz_but_ifa_")==0);
  if (iModified==0 && bCheckIsNew && this.isEntityNew()) {
    this.m_iModified = 1;
    this.m_msModified.push("L'entité n'a pas été sauvegardée.");
    iModified = 1;
  }

  if (iModified>0) {
    if (p_sFollowingUrl || p_sFollowingExpr) {
      this.doShowValidation(p_sFollowingUrl, p_sFollowingExpr, p_bInLine, p_oClickedButton);
      return false;

    } else { // en principe on ne passe plus jamais là
      var s = "Des modifications n'ont pas été sauvegardées :";
      for (var i=0; i<this.m_msModified.length; i++) {
        s+="\n - " + this.m_msModified[i];
      }
      s += "\nContinuer malgré tout ?"
      if (!confirm (s)) return false;
    }

  } else { // si pas de modif, exécution immédiate de l'action
    if (this.isEntityNew() && p_oClickedButton.id.indexOf("pbCancel")==-1) {
      this.validate();
      if (this.moErrorList.length>0) {
        this.doShowErrors();
        return false;
      }
    }
    var sAction = p_sFollowingExpr && p_sFollowingExpr!=""
      ? p_sFollowingExpr
      : "navigate(\"" + p_sFollowingUrl + "\")";
    var oFunc = new Function("e", sAction);
    oFunc.apply(p_oClickedButton, [e]);
  }
  return true;
}

//--------------  checkMulti ----------------
/**
 * Vérifie qu'un multi n'est pas encore en mode édition
 *
 * @return le message d'alerte éventuel si un multi n'est pas fermé
 */
ODYForm.prototype.checkMulti = function() {
  if (!this.m_moTab) return 0; // pour les table_form
  if (!this.m_oDOMForm.isValid()) return 0; // si le form n'est plus affiché, exit (suite à une navigation rapide par ex)
  var sMsg;
  // parcours des onglets
  this.m_moTab.each(function(oOFT) {
    // parcours des multi du tab
    if (!oOFT.m_oDOMForm) return;
    var moMulti = oOFT.m_oDOMForm.parentNode.select('div[id^=ifa_]') || [];
    moMulti.each(function(oDiv) {
      if (oDiv.select('a[id^=pbSave]').length!=0) { // s'il y a un <a> dont l'id commence par "pbSave" == bouton "Enregistrer"
        /* décompte des valeurs modifiées - désactivé dans un premier temps => on force le retour à la liste dans tous les cas
        var oMultiForm = oDiv.select('form[id^=frmMainInput]').first();
        var oOFMulti = ODYFormRegistry.getODYFormByFormName(oMultiForm.id);
        var iModifiedInMulti = oOFMulti.countModifiedFields();
        */
        var sLabId = oDiv.id.replace("ifa_","lab_");
        var oLab = $O2(sLabId,oOFT.m_oDOMForm);
        sMsg = (sMsg ? sMsg + "\n" : "") + "  - '" + (oLab ? oLab.textContent : oDiv.id) + "'";
      }
    });
  });
  if (sMsg) sMsg = "Attention données non enregistrés dans le(s) cadre(s) :\n\n" + sMsg 
    + "\n\n\n Merci de retourner à l'affichage de la liste dans chacun de ce(s) cadre(s), en enregistrant ou annulant les modifications";
  return sMsg;
}

//--------------  countModifiedFields ----------------
/**
 * @return le nombre d'item dont la valeur a été modifiée
 */
ODYForm.prototype.countModifiedFields = function() {
  this.m_iModified = 0;
  var sMode = this.getDisplayMode();
  if (sMode && sMode=="VISU") return this.m_iModified;
  this.m_msModified = new Array();
  var iProcessed = this.scanForm("countModifiedFieldsAction"
    , "alert((oFrame ? oFrame.name + ' : ' : '') + 'La fiche n\\\'est pas entierement chargee.')");

  return iProcessed==-1 ? -1 : this.m_iModified;
}

//--------------  countModifiedFieldsAction ----------------
function countModifiedFieldsAction(p_oMainFormElem, p_oFrameElem, p_sTabName) {
  var sCurrentValue = getCurrentValue(p_oFrameElem).trim();
  var sOriginValue  = getOriginValue(p_oFrameElem).trim();
	sOriginValue = sOriginValue.replace(/\r\n/g,"\n");
	if ( sCurrentValue.indexOf(ODYForm.sVALUE_SEP,0) > -1 ) {
	  sCurrentValue = sCurrentValue.split(ODYForm.sVALUE_SEP).sort().join(ODYForm.sVALUE_SEP);
  }
  if ( sOriginValue.indexOf(ODYForm.sVALUE_SEP,0) > -1 ) {
  	sOriginValue = sOriginValue.split(ODYForm.sVALUE_SEP).sort().join(ODYForm.sVALUE_SEP);
  }
  var bModified     = (sCurrentValue != sOriginValue);
  if (bModified) {
    var sLabel = getInputLabel(p_oFrameElem);
    if (sOriginValue.length>15) sOriginValue = sOriginValue.substring(0,15) + "[...]";
    if (sCurrentValue.length>15) sCurrentValue = sCurrentValue.substring(0,15) + "[...]";
    sLabel += " ('"+sOriginValue+"' => '"+ sCurrentValue + "')";
    // NB : this pointe bien vers l'oOF, cf. appel oFunc.call() dans scanForm()
    this.m_msModified.push(sLabel);
    // showInputInfo(p_oFrameElem);
    this.m_iModified++;
  }
}

//--------------  locallyCommit ----------------
/**
 *  Applique les modifications au formulaire : modif de l'attribut "originalValue" (la maj du champ hidden de 
 *      la frame "button" est fait lors du scanForm de send())
 * @param p_iEntId  id de l'entité modifiée (utile si enregistrement d'une entity new)
 * @param p_iETId id du type de l'entité (utile si enregistrement d'une entity new)
 * @return le nombre d'item dont la valeur a été modifiée
 */
ODYForm.prototype.locallyCommit = function(p_iEntId, p_iETId) {
  this.m_iModified = 0;
  this.m_msModified = new Array();
  if (this.isEntityNew() && p_iEntId) {
    this.setEntityId(p_iEntId); // met à jour l'id (évite de répéter l'alerte "entité non sauvegardée")
  }
  var iProcessed = this.scanForm(function(p_oMainFormElem, p_oFrameElem, p_sTabName) {
      var sCurrentValue = getCurrentValue(p_oFrameElem);
      var sOriginValue  = getOriginValue(p_oFrameElem);
      sOriginValue = sOriginValue.replace(/\r\n/g,"\n");
      var bModified     = (sCurrentValue != sOriginValue);
      if (!bModified) return;
      setOriginValue(p_oFrameElem, sCurrentValue);
//      this.setFieldValue(p_oFrameElem.id,sCurrentValue);
      this.m_iModified++;
    }
    , "alert(oFrame.name + ' : La fiche n\\\'est pas entierement chargee.')");
  return iProcessed==-1 ? -1 : this.m_iModified;
}

// =========================================================================
// @class
// ODYFormTab - Un onglet dans un formulaire Odyssée
//
// @param 
// : p_iId    id (numérique) correspondant à l'id de base du DisplayTab
// : p_sLib   le libellé affiché du tab
// =========================================================================

ODYFormTab = function(p_iId, p_sLib) {
  this.m_iId = p_iId;  // le display_tab.id
  this.m_sLib = p_sLib; // le libellé de l'onglet
  this.m_oParentODYForm = null; //
  this.m_oDOMForm = null;  // le formulaire contenu dans l'onglet
  this.m_oCont    = null; // le container qui correspond à cet onglet
  this.m_iIndex = null;   // n° d'ordre de ce tab [1,n]
}
ODYFormTab.sSPAN_ID_PREFIX = "tabs_";

// ---------- getInputByDisplayItemId ----------------
ODYFormTab.prototype.getInputByDisplayItemId = function(p_iDispiId) {
  if (null==this.m_oDOMForm) return;
  var oInput = Form.getElements(this.m_oDOMForm).find( function(element) {
      return element.getDisplayItemId() == p_iDispiId;
    });
    if (!oInput) return;
  oInput.m_oTab = this;
  return oInput;
}
// ---------- getInputByAttributeSn ----------------
ODYFormTab.prototype.getInputByAttributeSn = function(p_sAttrSn) {
  if (null==this.m_oDOMForm) return;
  var oInput = Form.getElements(this.m_oDOMForm).find( function(element) {
      return element.getAttributeName()==p_sAttrSn;
    });
  if (!oInput) return;
  if (oInput.id.match("libt_.*")) oInput = this.m_oDOMForm.findChildById(oInput.id.substring(3));
  oInput.m_oTab = this;
  return oInput;
}

// ---------- load ----------------
/*
 * Charge le contenu de l'onglet (=de la div) si pas déjà chargé
 * @return true si l'onglet a été, ou était chargé. false si chargement impossible
 */
ODYFormTab.prototype.load = function() {
  var vChildren = this.m_oParentODYForm.getMainContainer().childNodes;
  // recherche la div nommée "frame_nn" ou "frame_nn.NNNNNN"
  for (var i=0,l=vChildren.length ; i<l ; i++) {
    var oDiv = vChildren[i];
    if (!oDiv) continue;
    if (oDiv.tagName!="DIV" || !oDiv.id || oDiv.id.indexOf("frame")==-1) continue;
    if (oDiv.id.match(new RegExp(".*_" + (this.m_iIndex+1) + "(\\.\\d+)?$"))) {
      if (!oDiv.isLoaded() && oDiv.src) display(oDiv, this.m_oParentODYForm.m_bIsWizard ? 60 :0, oDiv.src);
      return true;
    }
  }
  return false;
}

// =========================================================================
// @class
// OInputError - Une erreur dans un formulaire Odyssée
// Ces objets encapsulent toutes les informations nécessaires à la génération des messages d'erreur.
//
// @param 
// : p_oInput    l'input (visible) dans lequel l'erreur s'est produite
// =========================================================================

OInputError = function(p_oInput) {
  this.m_oInput = p_oInput;
  this.m_sType = "input"; // type de l'erreur : "input" ou "system"
  this.m_sMsg = null;
  this.m_sValue = null;  // la valeur de l'input (valeur du param http)
  this.m_sText = null;  // le text affiché par l'input
  this.m_iEntityId = null;
  this.m_sEntityDesignation = null;
  this.m_sTabName = null;
  this.m_sFieldName = null;
  this.m_sLevel = null;
  this.m_sHTML = '<div class="msg-alerte">\
      <img src="/com/images/alert/#level#.png" align="left" onClick="this.nextSibling.toggle()"/><span\
       style="font:11px lighter;color:black;display:none;">[#desi#]</span>\
      <span class="msg">#fieldvalue#&#160;&#160;&#160;#msg#\
      </span>\
    </div>';
}

OInputError.prototype.buildHTML = function(p_sPreviousTabName) {
  var s = this.m_sHTML;
  // nom de l'image
  s = s.replace(/#level#/, this.m_sLevel ? this.m_sLevel.toLowerCase() : "error");
  // rupture sur l'éventuel nom de l'onglet
  if (this.m_sTabName && p_sPreviousTabName!=this.m_sTabName) s = '<span class="catMsg">' + this.m_sTabName + '</span>' + s;
  var sFieldNameAndValue = "";
  if (this.m_sFieldName) {
    sFieldNameAndValue += '<span class="grasViolet">' + this.m_sFieldName + '</span>';
    if (this.m_sValue) sFieldNameAndValue+= "&#160;";
  }
  if (this.m_sValue) {
    sFieldNameAndValue += '<input type="text" class="hasError" size="' + (this.m_sText.length) + '" value="' + this.m_sText + '">';
  }
  if (this.m_sFieldName || this.m_sValue) sFieldNameAndValue += "<br>";
  s = s.replace(/#fieldvalue#/, sFieldNameAndValue);
  var sMsg = this.m_sMsg;
  if (this.m_sType=="system") sMsg = '<a href="#" onclick="this.findBrotherById(\'errDetails\',\'textarea\').show();this.hide()">( details ... )</a>'
    + '<textarea id="errDetails" cols="50" rows="7">' + sMsg + "</textarea>";
  s = s.replace(/#msg#/, sMsg);
  // la désignation de l'entité concernée
  var sDesi = "";
  if (this.m_sEntityDesignation) {
    sDesi = this.m_iEntityId + '-' + this.m_sEntityDesignation;
  }
  s = s.replace(/#desi#/, sDesi);
  
  return s;
}

// =========================================================================
//     FONCTIONS GENERALES
// =========================================================================

//--------------  doAction ----------------
/**
 * Wrapper pour lancer une action avec blocage du "reclic" + waiting + hideError à la fin
 */
function doAction(p_oClicked, p_oEvent, p_sActionExpr) {
  var oAlertDiv = Event.element(e);
  while (oAlertDiv.parentNode && (oAlertDiv.id!="alerte")) oAlertDiv = oAlertDiv.parentNode;
  oAlertDiv.hide();
  var oCont = oAlertDiv.getContainer();
  if (!oCont) return;
  var oFondDiv = ODYForm.getFondGrisDiv(oCont);
  if (oFondDiv) oFondDiv.hide();
  return false;
}

//--------------  hideErrors ----------------
/**
 * Masque le message d'erreur. Appelé depuis le conteneur dans lequel se trouve la div à masquer.
 * @param e l'event à l'origine de l'action
 * @param p_sAlertDivId l'id de la div alerte concernée (paramètre alternatif à e)
 * 
 */
function hideErrors(e, p_sAlertDivId) {
  if (p_sAlertDivId && typeof(p_sAlertDivId)!="string") p_sAlertDivId = p_sAlertDivId.id;
  var sDivId = p_sAlertDivId ? p_sAlertDivId : "alerte";
  var oAlertDiv = e ? Event.element(e) : $O2(p_sAlertDivId);
  while (oAlertDiv.parentNode && oAlertDiv.id!=sDivId) oAlertDiv = oAlertDiv.parentNode;
  oAlertDiv.hide();
  var oCont = oAlertDiv.getContainer();
  if (!oCont) return;
  var oFondDiv = ODYForm.getFondGrisDiv(oCont);
  if (oFondDiv) oFondDiv.hide();
}
