/**
 * Saut de ligne pour éviter la multiplication des antislashes dans le code INSTANCE/PHP/JS/HTML
 * @public
 */
var CR = "\n";

/**
 * Raccourci vers le document
 * @public
 */
var $D = document;

/**
 * Raccourci vers window
 * @public
 */
var $W = window;

/**
 * Namespace principal
 * @public
 */
var AI =
{
  /**
   * Placeholder pour les objets dont on veut garder une référence globale
   * @public
   * @deprecated : lui préférer $G
   */
  _GLOBALS:{},

  /**
   * Placeholder pour les objets dont on veut garder une référence globale
   * @public
   */
  $G:{},

  /**
   * Définition de l'heure de démarrage du système
   */
  startingTime:(new Date()).valueOf(),

  /**
   * Constante : Non défini
   * @public
   * @deprecated : lui préférer $UNDEF
   */
  STATE_UNDEFINED:0,

  /**
   * Constante : Non défini
   * @public
   */
  $UNDEF:0,

  /**
   * Constante : En chargement
   * @public
   * @deprecated : lui préférer $LOADING
   */
  STATE_LOADING:1,

  /**
   * Constante : En chargement
   * @public
   */
  $LOADING:1,

  /**
   * Constante : Chargé et prêt
   * @public
   * @deprecated : lui préférer $LOADED
   */
  STATE_LOADED:2,

  /**
   * Constante : Chargé et prêt
   * @public
   */
  $LOADED:2,

  /**
   * Constante : En erreur
   * @public
   * @deprecated : lui préférer $ERR
   */
  STATE_ERROR:3,

  /**
   * Constante : En erreur
   * @public
   */
  $ERR:3,

  /**
   * Liste des fichiers avec leurs éventuels callbacks et leur statut de chargement
   * @public
   */
  $f:{},

  /**
   * Inclut le fichier et déclenche les différents callbacks selon l'évolution du chargement
   * @param[in] {string} f  Nom du fichier à charger (si c'est une adresse relative, elle est relative à /js/ai/)
   * @param[in] {object} c2 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state $LOADED (optional)
   * @param[in] {object} c1 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state $LOADING (optional)
   * @param[in] {object} c3 Référence de la fonction (callback) a exécuter lorsque le fichier passe en state $ERR (optional)
   * @public
   */
  require:function(f, c2, c1, c3)
  {
    var url = f.charAt(0) == '/' ? f : '/js/ai/' + f;
    var H = $D.getElementsByTagName("head")[0];
    var S = $D.createElement("script");

    AI.$f[f] =
    {
      state:AI.$UNDEF,
      onloading:c1,
      onloaded:c2,
      onerror:c3 ? c3 : function() { alert(f + CR + 'Erreur de chargement du fichier' + CR + 'Error loading file'); }
    };

    S.id = 'script_' + f.replace(/[^a-z0-9]/i, '_');
    S.type = "text/javascript";
    S.src = url;

    AI.setFileState(f, AI.$LOADING);

    H.appendChild(S);
  },

  /**
   * Définit le statut de chargement du fichier et déclenche l'eventuel callback associé à cet état
   * @param[in] {string}  f Nom du fichier de référence
   * @param[in] {integer} s State à appliquer
   * @public
   */
  setFileState:function(f, s)
  {
    var C = null;

    AI.$f[f].state = s;

    switch ( s )
    {
      case AI.$LOADING:
        C = AI.$f[f].onloading;
      break;

      case AI.$LOADED:
        C = AI.$f[f].onloaded;
      break;

      case AI.$ERROR:
        C = AI.$f[f].onerror;
      break;
    }

    if ( C ) { C(); }
  },

  /**
   * Définit une propriété de chargement du fichier (changement d'un callback par exemple)
   * @param[in] {string}         f Nom du fichier de référence
   * @param[in] {string}         p Nom de la propriété à manipuler (state, onloading, onloaded, onerror)
   * @param[in] {integer|object} v Valeur (integer pour state, référence de fonction pour les autres)
   * @public
   */
  setFileProperty:function(f, p, v) { AI.$f[f][p] = v; },

  /**
   * Obtient le statut courant du fichier
   * @param[in] {string} f Nom du fichier de référence
   * @public
   * @return {integer} L'état courant
   */
  getFileState:function(f) { return AI.$f[f].state; },

  /**
   * Déclenche une alerte, méthode surchargée au fur et à mesure que le système se met en place
   * @param[in] {string} t Texte du message
   * @private
   */
  _alertBox:function(t) { $W.alert(t); },

  /**
   * Déclenche une erreur, méthode surchargée au fur et à mesure que le système se met en place
   * @param[in] {string} t Texte du message
   * @private
   */
  _throwError:function(t) { throw new Error(t); }
  
  /**
   * Charge un script non AI et appelle une fonction au chargement
   * @param {string} U (Url)      Source url of the file to load
   * @param {object} C {Callback} Callback function to launch once ready (optional)
   * @param {object} O (scOpe)    Application scope for the callback function (optional)
   * @param {object} B (Bonus}    Arbitrary object send as a param to the callback function (optional)
   * @public
   */
/*
  ,loadBack:function(U, C, O, B)
  {
    var T = IS.ie() "onreadystatechange" : "onload",
      S = $E('script', {type:"text/javascript", src:U});
    if ( C )
    {
      S[T] = function()
      {
        if ( IS.ie() && ! ( /loaded|complete/.test($W.event.srcElement.readyState) ) )
        {
          return;
        }
        C.call(O ? O : this, B);
        S[T] = null;
        return;
      };
    }
    DOM.head.appendChild(S);
  }
*/
};

/**
 * Détermine si le contexte d'éxécution correspond est le système principal ou un sous système
 * @public
 * @return {boolean} true si on est bien dans une fenêtre ouverte par le système principal, false dans le cas contraire
 */
function isInsideFenetreAI()
{
  return typeof top.isMainWidget !== 'undefined' && top.isMainWidget;
}

/**
 * Déclenche une alerte et une erreur
 * @param[in] {string} t Texte du message
 * @public
 */
function alertError(t)
{
  AI._alertBox(t);
  AI._throwError(t);
}

/**
 * Définition des constantes DOM indispensables (ELEMENT_NODE et TEXT_NODE) que certains clients n'exposent pas (comme IE)
 * @public
 */
if ( typeof $D.ELEMENT_NODE == 'undefined' || $D.ELEMENT_NODE === null ) { $D.ELEMENT_NODE = 1; }
if ( typeof $D.TEXT_NODE == 'undefined' || $D.TEXT_NODE === null ) { $D.TEXT_NODE = 3; }


var bench =
{
  D:0,
  T:'',
  init:function(T)
  {
    bench.T = T;
    bench.D = (new Date()).valueOf();
  },
  fin:function()
  {
    return (new Date()).valueOf() - bench.D;
  }
};
/**
 * Module utilitaire générique
 * @public
 */
AI.utils =
{
  /**
   * Détermine si la référence est définie
   * @param {object} o La référence
   * @param {object} k La clé ou l'index si la référence est un tableau
   * @public
   * @return {boolean} true si définie, false dans le cas contraire
   */
  defined:function(o, k)
  {
    if ( k )
    {
      return typeof o[k] != 'undefined';
    }
    return typeof o != 'undefined';
  },

  /**
   * Renvoit toujours true
   * @public
   * @return {boolean} true
   */
  returnTrue:function() { return true; },

  /**
   * Renvoit toujours false
   * @public
   * @return {boolean} false
   */
  returnFalse:function() { return false; },

  /**
   * Renvoit toujours null
   * @public
   * @return {object} null
   */
  returnNull:function() { return null; },

  /**
   * Renvoit toujours this
   * @public
   * @return {object} this
   */
  returnThis:function() { return this; },

  /**
   * Renvoit toujours 0
   * @public
   * @return {integer} 0 (zero)
   */
  returnZero:function() { return 0; },

  /**
   * Renvoit toujours -1
   * @public
   * @return {integer} -1
   */
  returnNegativeIndex:function() { return -1; },

  /**
   * Vérifie si un objet est vide
   * @param[in] {object} o Object à vérifier
   * @public
   * @return {boolean} true si vide, false si il possède au moins une clé
   */
  isObjectEmpty:function(o)
  {
    for ( var k in o )
    {
      return false;
    }
    return true;
  },

  /**
   * Calcule la taille d'un objet (le nombre de clés) sur le même principe qu'un tableau possède une propriété length
   * @param[in] {object} o Object
   * @public
   * @return {integer} La taille de l'objet
   */
  getObjectLength:function(o)
  {
    var r = 0;
    for ( var k in o )
    {
      r++;
    }
    return r;
  },
  /**
   * Convertit un objet en array
   * @param[in] {object} o Objet à transformer
   * @public
   * @return {array} Objet initial sous forme de tableau
   */
  convertObjectToArray:function(o)
  {
    var r = [];
    for ( var k in o )
    {
      r.push(k);
    }
    return r;
  },

  /**
   * Convertit un objet en string
   * @param[in] {object} o Objet à transformer
   * @param[in] {string} s Séparateur (optional)
   * @public
   * @return {array} Objet initial sous forme de string
   */
  convertObjectToString:function(o, s)
  {
    return AI.utils.convertObjectToArray(o).join(s ? s : ', ');
  },

  /**
   * Convertit les valeurs d'un objet est un tableau
   * @param[in] {object} o Objet à transformer
   * @public
   * @return {array} Objet initial sous forme de tableau
   */
  convertObjectValuesToArray:function(o)
  {
    var r = [];
    for ( var k in o )
    {
      r.push(o[k]);
    }
    return r;
  },

  /**
   * Convertit l'objet argument en un tableau
   * @param[in] {object} a Argument à transformer
   * @public
   * @return {array} Argument initial sous forme de tableau
   */
  convertArgumentsToArray:function(a)
  {
    var b = [];
    for ( var i = 0, m = a.length; i < m; i++ )
    {
      b.push(a[i]);
    }
    return b;
  },

  /**
   * Convertit une dimensions CSS exprimées en version courte (margin, padding, etc. sur 1, 2, 3 ou 4 paramètres) en un tableau indéxé
   * @param[in] D La dimensions à splitter en 4 valeurs
   * @public
   * @return {array} Les 4 dimensions [Top, Right, Bottom, Left]
   */
  convertShortHandToArray:function(D)
  {
    var m = D.length;
    if ( m > 4 )
    {
      alertError(D + CR + "Nombre d'arguments invalides");
    }

    var v;
    var R = [];

    for ( var i = 0; i < m; i++ )
    {
      v = D[i];

      if ( AI.utils.isValidNumber(v) )
      {
        R.push(v);
      }
      else if ( !AI.utils.isValidString(v) )
      {
        R.push(null);
      }
      else
      {
        alertError(v + CR + "Valeur courte invalide");
      }
    }

    // Fix Values (Shorthand)
    switch ( m )
    {
      case 1:
        R[1] = R[2] = R[3] = R[0];
      break;

      case 2:
        R[2] = R[0];
        R[3] = R[1];
      break;

      case 3:
        R[3] = R[1];
      break;
    }

    return R;
  },

  /**
   * Determine si c'est un email
   */
  isEmail:function(M)
  {
    return !M.search(/^[\w\-][\w\-\.]+@[\w\-]+\.[a-zA-Z]{2,6}$/);
  },

  /**
   * Détermine si le paramètre est valide
   * @param[in] {object} v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est valide
   */
  isValid:function(v)
  {
    var R = false;
    switch ( typeof v )
    {
//      case "undefined":
//        R = false;
//      break;
      case "object":
        R = v !== null;
      break;

      case "string":
        R = v !== "";
      break;

      case "number":
        R = !isNaN(v);
      break;

      case "function":
      case "boolean":
        R = true;
      break;
    }
    return R;
  },

  /**
   * Détermine si le paramètre est un nombre valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un nombre valide
   */
  isValidNumber:function(v) { return typeof v === "number" && !isNaN(v); },

  /**
   * Détermine si le paramètre est un string valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un string valide
   */
  isValidString:function(v) { return typeof v === "string" && v !== ""; },

  /**
   * Détermine si le paramètre est un array valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un tableau valide
   */
  isValidArray:function(v) { return typeof v === "object" && v !== null && v instanceof Array; },

  /**
   * Détermine si le paramètre est un objet valide (sans être un array)
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un objet valide et pas un tableau
   */
  isValidObject:function(v) { return typeof v === "object" && v !== null && !(v instanceof Array); },

  /**
   * Détermine si le paramètre est une fonction valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est une fonction valide
   */
  isValidFunction:function(v) { return typeof v === "function"; },
  
  /**
   * Détermine si le paramètre est un boolean valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un boolean valide
   */
  isValidBoolean:function(v) { return typeof v === "boolean"; },

  /**
   * Détermine si le paramètre est un string ou un nombre valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un nombre ou un string valide
   */
  isValidStringOrNumber:function(v)
  {
    if ( typeof v == 'string' )
    {
      return v !== '';
    }
    else if ( typeof v == 'number' )
    {
      return !isNaN(v);
    }
    return false;
  },

  /**
   * Détermine si le paramètre est un élément HTML valide
   * @param[in] v Valeur à vérifier
   * @public
   * @return {boolean} true si le paramètre est un HTMLElement valide
   */
  isValidElement:function(v)
  {
    return typeof v === 'object' && v !== null || v.nodeType !== $D.ELEMENT_NODE;
  },

  /**
   * Génère un identifiant unique
   * @param[in] {string} p Préfixe (optional)
   * @public
   * @return {string} Un identifiant unique
   */
  uniqid:function(P)
  {
    return ( P ? P : '' ) + (new Date().valueOf());
  },

  /**
   * Gestion des cookies
   */
  Cookies:
  {
    /**
     * Défini un cookie
     * @param {string} N [Name]  Nom
     * @param {string} V [Value] Valeur
     * @param {string} D [Days]  Jours de validité
     * @public
     */
  	set:function(N, V, D)
    {
      var d = new Date(); /* date de travail */
      D = D || 30;
      d.setTime(d.getTime() + (D * 24 * 60 * 60 * 1000));
  		$D.cookie = N + "=" + V + "; expires=" + d.toGMTString() + "; path=/";
  	},
  	/**
     * Lit un cookie
     * @param {string} N [Name] Nom
     * @public
     * @return {string} La valeur du cookie
     */
  	get:function(N)
    {
  		var ca = $D.cookie.split(';'),i,c;
      N += '=';
  		for( i = 0; i < ca.length; i++ )
      {
  			c = ca[i];
  			while ( c.charAt(0) == ' ' )
        {
          c = c.substring(1,c.length);
        }
  			if ( c.indexOf(N) === 0 )
        {
          return c.substring(N.length,c.length);
        }
  		}
  		return null;
    }
  }

};

/**
 * Version plus intelligente du alert standard. Celui-ci peut être interrompu
 * @param[in] {string] t Texte du message
 * @public
 */
function alerter(t)
{
  if ( alerter.$ )
  {
    alerter.$ = $W.confirm(t);
  }
}

/**
 * Flag privé de la fonction alerter, permet d'annuler les alertes afin d'éviter les débuggages en boucle qu'on peut plus annuler autrement que par ALT+CTLR+DEL
 * @private
 */
alerter.$ = true;

/**
 * Déclenche une alerte, redéfinition de la méthode standard prenant en charge l'arleter amélioré
 * @todo Il doit y a voir une raison, mais je sais pas pourquoi on vérifie si alerter existe. Il faut vérifier si ça sert réellement à qqchose et si ce serait pas mieux de faire bettement    alerter(t + CR + CR + 'Continuer le pistage des erreurs ?');
 * @param[in] {string} t Texte du message
 * @private
 */
AI._alertBox = function(t)
{
  var C = typeof alerter == 'function' ? alerter : $W.alert;
  C(t + CR + CR + 'Continuer le pistage des erreurs ?');
};

/*
---------------------------------------------------------------------------
  ALIAS
---------------------------------------------------------------------------
*/

var defined = AI.utils.defined;
/**
 * Module de traduction des textes
 * @public
 */
AI.traduction =
{
  /**
   * Tableau de correspondances
   * @private
   */
  $:{},
  
  /**
   * Tableau des correspondes non trouvées, utilisé pour mettre à jour par AJAX pour les prochains appels
   * @private
   */
//  $$:[],
  
  /**
   * Ajoute une paire clé / valeur
   * @param[in] {string} K Clé
   * @param[in] {string} V Valeur
   * @public
   */
  add:function(K, V)
  {
    AI.traduction.$[K] = V;
  },
  
  /**
   * Synchronize les clés de $$ vers $
   * @private
   */
/*
   synchro:function()
   {
      if ( AI.traduction.$$.length > 0 )
      {
        if ( AI.traduction.toid )
        {
          $W.clearTimeout(AI.traduction.toid);
        }
        AJAX_POST(
          '/AJAX/traduction_synchro.php',
          {K:AI.traduction.$$.join(',')},
          // faire les callbacks
          ok, erreur, run, oScope, objAccompagne
        );
   },
*/

  /**
   * Obtient la valeur d'une clé
   * @param[in] {string} K Clé
   * @public
   * @return {string} La valeur, ou la clé si inconnue
   */
  get:function(K)
  {
    var T = AI.traduction.$;
//    return ( typeof T[K] === 'undefined' || T[K] === null ) ? K : T[K];
    return ( !defined(T, K) || T[K] === null ) ? K : T[K];
  }
};

/**
 * Alias helper
 * @see AI.traduction.get
 * @public
 */
var __ = AI.traduction.get;

/**
 * Alias helper
 * @see AI.traduction.add
 * @public
 */
var i18ln = AI.traduction.add;
/**
 * Ajout de méthodes à l'objet Array
 */

if ( !Array.prototype.push )
{
  Array.prototype.push = function()
  {
    var L = this.length;
    for ( var i = 0; i < arguments.length; i++)
    {
      this[L + i] = arguments[i];
    }
    return this.length;
  };
}

// Some of them from Erik Arvidsson <http://erik.eae.net/>
// More documentation could be found here:
// http://www.webreference.com/programming/javascript/ncz/column4/
// An alternative implementation can be found here:
// http://www.nczonline.net/archive/2005/7/231

// Mozilla 1.8 has support for indexOf, lastIndexOf, forEach, filter, map, some, every
// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf

if ( !Array.prototype.indexOf )
{
  Array.prototype.indexOf = function(o, from)
  {
    if ( !from )
    {
      from = 0;
    }
    else if ( from < 0 )
    {
      from = Math.max(0, this.length + from);
    }

    for ( var i = from, m = this.length; i < m; i++ )
    {
      if ( this[i] === o )
      {
        return i;
      }
    }

    return -1;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:lastIndexOf
if ( !Array.prototype.lastIndexOf )
{
  Array.prototype.lastIndexOf = function(o, from)
  {
    if ( !from )
    {
      from = this.length - 1;
    }
    else if ( from < 0 )
    {
      from = Math.max(0, this.length + from);
    }

    for ( var i = from; i >= 0; i-- )
    {
      if ( this[i] === o )
      {
        return i;
      }
    }

    return -1;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:forEach
if ( !Array.prototype.forEach )
{
  Array.prototype.forEach = function(f, o)
  {
    // 'l' must be fixed during loop... see docs
    var l = this.length;

    for ( var i = 0; i < l; i++ )
    {
      f.call(o, this[i], i, this);
    }
  };
}

if ( !Array.prototype.forEachObject )
{
  Array.prototype.forEachObject = function(f)
  {
    // 'l' must be fixed during loop... see docs
    var l = this.length;
    for ( var i = 0; i < l; i++ )
    {
      f.call(this[i], this, i);
    }
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:filter
if ( !Array.prototype.filter )
{
  Array.prototype.filter = function(f, o)
  {
    // 'l' must be fixed during loop... see docs
    var l = this.length;
    var r = [];

    for ( var i = 0; i < l; i++ )
    {
      if ( f.call(o, this[i], i, this) )
      {
        r.push( this[i] );
      }
    }

    return r;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:map
if ( !Array.prototype.map )
{
  Array.prototype.map = function(f, o)
  {
    var l = this.length;  // must be fixed during loop... see docs
    var r = [];

    for ( var i = 0; i < l; i++ )
    {
      r.push( f.call(o, this[i], i, this) );
    }

    return res;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:some
if ( !Array.prototype.some )
{
  Array.prototype.some = function(f, o)
  {
    // 'l' must be fixed during loop... see docs
    var l = this.length;  
    for ( var i = 0; i < l; i++ )
    {
      if ( f.call(o, this[i], i, this) )
      {
        return true;
      }
    }

    return false;
  };
}

// http://developer-test.mozilla.org/docs/Core_JavaScript_1.5_Reference:Objects:Array:every
if ( !Array.prototype.every )
{
  Array.prototype.every = function (f, o)
  {
    // 'l' must be fixed during loop... see docs
    var l = this.length;  
    for ( var i = 0; i < l; i++ )
    {
      if ( !f.call(o, this[i], i, this) )
      {
        return false;
      }
    }

    return true;
  };
}

if ( !Array.prototype.contains )
{
  Array.prototype.contains = function(obj)
  {
    return this.indexOf(obj) != -1;
  };
}

if ( !Array.prototype.copy )
{
  Array.prototype.copy = function(obj)
  {
    return this.concat();
  };
}

/*
if (!Array.prototype.clone)
{
  Array.prototype.clone = function(obj)
  {
    return this.concat();
  };
}
*/

/*
if (!Array.prototype.append)
{
  Array.prototype.append = function ()
  {
    for (var i=0, l=arguments.length; i<l; i++)
    {
      this[this.length] = arguments[i];
    }
    return this;
  };
}
*/
if ( !Array.prototype.insertAt )
{
  Array.prototype.insertAt = function(o, i)
  {
    this.splice(i, 0, o);
  };
}

if (!Array.prototype.insertBefore)
{
  Array.prototype.insertBefore = function(o, o2)
  {
    var i = this.indexOf(o2);
    if ( i == -1 )
    {
      this.push(o);
    }
    else
    {
      this.splice(i, 0, o);
    }
  };
}

if ( !Array.prototype.insertAfter )
{
  Array.prototype.insertAfter = function(o, o2)
  {
    var i = this.indexOf(o2);
    if ( i == -1 || i == (this.length-1) )
    {
      this.push(o);
    }
    else
    {
      this.splice(i+1, 0, o);
    }
  };
}

if ( !Array.prototype.removeAt )
{
  Array.prototype.removeAt = function(i)
  {
    return this.splice(i, 1);
  };
}

if ( !Array.prototype.remove )
{
  Array.prototype.remove = function(o)
  {
    var i = this.indexOf(o);
    if ( i != -1 )
    {
      return this.splice(i, 1);
    }
  };
}

if ( !Array.prototype.removeAll )
{
  Array.prototype.removeAll = function()
  {
    return this.splice(0, this.length);
  };
}

if ( !Array.prototype.getLast )
{
  Array.prototype.getLast = function()
  {
    return this[this.length-1];
  };
}

if ( !Array.prototype.getFirst )
{
  Array.prototype.getFirst = function()
  {
    return this[0];
  };
}

if ( !Array.prototype.in_array )
{
  Array.prototype.in_array = function(item)
  {
    return this.indexOf(item) != -1;
  };
}
/**
 * Ajout de méthodes à l'objet String
 */
String.prototype.contains = function(s) { return this.indexOf(s) != -1; };
String.prototype.toFirstUp = function() { return this.charAt(0).toUpperCase() + this.substr(1); };

String.prototype.toCamelCase = function()
{
  var A = this.split("-");
  var L = A.length;

  if ( L == 1 )
  {
    return A[0];
  }

  var N = this.indexOf("-") === 0 ? A[0].charAt(0).toUpperCase() + A[0].substring(1) : A[0];

  for ( var p, i = 1; i < L; i++ )
  {
    p = A[i];
    N += p.charAt(0).toUpperCase() + p.substring(1);
  }

  return N;
};

String.prototype.trimLeft = function() { return this.replace(/^\s+/, ""); };
String.prototype.trimRight = function() { return this.replace(/\s+$/, ""); };
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); };

String.prototype.add = function(v, sep)
{
  if ( this == v )
  {
    return this;
  }
  else if ( this === "" )
  {
    return v;
  }
  else
  {
    if ( !AI.utils.isValid(sep) ) { sep = ","; }

    var a = this.split(sep);

    if ( a.indexOf(v) == -1 )
    {
      a.push(v);
      return a.join(sep);
    }
    else
    {
      return this;
    }
  }
};

String.prototype.remove = function(v, sep)
{
  if ( this == v || this === "" )
  {
    return "";
  }
  else
  {
    if ( !AI.utils.isValid(sep) ) { sep = ","; }

    var a = this.split(sep);
    var p = a.indexOf(v);

    if ( p == -1 ) { return this; }

    do { a.splice(p, 1); }
    while( ( p = a.indexOf(v) ) != -1 );

    return a.join(sep);
  }
};

String.prototype.stripTags = function()
{
  return this.replace(/<\/?[^>]+>/gi, "");
};

/**
 * un string qui doit etre utilisé dans une regexp doit etre encodé d'abord
 * si ce string peut avoir des caractères spéciaux comme la chaine suivante
 * var testString = 'http://www.example/index.htm?page=faq[]()|\\+*'
 */
String.prototype.encodeRE = function()
{
  return this.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1');
};

/*

String.prototype.repeat = function(l){
	return new Array(l+1).join(this);
};

*/

/**
 * Convertit un texte CamelCase au format hyphen (BorderTopMargin = border-top-margin)
 * @param {string} t Texte à transformer
 * @public
 * @return {string} La version avec des tirets du texte CamelCase
 */
function CamelToHyphen(t)
{
  var R = '';
  for ( var i = 0, m = t.length; i < m; ++i )
  {
    if ( t.charAt(i) == t.charAt(i).toUpperCase() )
    {
      R = R + '-' + t.charAt(i).toLowerCase();
    }
    else
    {
      R = R + t.charAt(i);
    }
  }
  return R;
}

/**
 * Convertit un texte avec des tirets au format CamelCase (border-top-margin = BorderTopMargin)
 * @param {string} t Texte à transformer
 * @public
 * @return {string} La version CamelCase du texte avec des tirets
 */
function HyphenToCamel(t)
{
  var R = '';
  return R;
}
/**
 * Ajout de méthodes à l'objet Number
 */

Number.prototype.limit = function(vmin, vmax)
{
  if (vmax !== null && typeof vmax == "number" && this > vmax)
  {
    return vmax;
  }
  else if (vmin !== null && typeof vmin == "number" && this < vmin)
  {
    return vmin;
  }
  else
  {
    // Number is needed, otherwise a object will be returned
    return Number(this);
  }
};

Number.prototype.inRange = function(vmin, vmax) { return this >= vmin && this <= vmax; };
Number.prototype.betweenRange = function(vmin, vmax) { return this > vmin && this < vmax; };

function intval(i)
{
  var R = parseInt(i, 10) || 0;
  R = isNaN(R) ? 0 : R;
  return R;
}
/**
 * Ajout de méthodes à l'objet Function
 */
// est-ce qu'on s'en sert vraiment ?
// implement function apply for browsers which don't support it natively
if ( !Function.prototype.apply )
{
  Function.prototype.apply = function(oScope, args)
  {
    var sarg = [];
    var rtrn, appel;

    if (!oScope) { oScope = window; }
    if (!args) { args = []; }

    for (var i = 0; i < args.length; i++) { sarg[i] = "args["+i+"]"; }

    appel = "oScope._applyTemp_(" + sarg.join(",") + ");";

    oScope._applyTemp_ = this;
    rtrn = eval(appel);

    delete oScope._applyTemp_;

    return rtrn;
  };
}

/**
 * TEST
 

// implement leak free closure
// http://laurens.vd.oever.nl/weblog/items2005/closures/
if (!Function.prototype.closure)
{
  Function.prototype.closure = function(obj)
  {
    // Init object storage.
    if (!window.__objs)
    {
      window.__objs = [];
      window.__funs = [];
    }

    // For symmetry and clarity.
    var func = this;

    // Make sure the object has an id and is stored in the object store.
    var objId = obj.__objId;
    if (!objId)
    {
      __objs[objId = obj.__objId = __objs.length] = obj;
    }

    // Make sure the function has an id and is stored in the function store.
    var funcId = func.__funcId;
    if (!funcId)
    {
      __funcs[funcId = func.__funcId = __funcs.length] = func;
    }

    // Init closure storage.
    if (!obj.__closures)
    {
      obj.__closures = [];
    }

    // See if we previously created a closure for this object/function pair.
    var closure = obj.__closures[funcId];
    if (closure)
    {
      return closure;
    }

    // Clear references to keep them out of the closure scope.
    obj = null;
    func = null;

    // Create the closure, store in cache and return result.
    return __objs[objId].__closures[funcId] = function ()
    {
      return __funcs[funcId].apply(__objs[objId], arguments);
    };
  };
}
*/
// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('compat/function.js', AI.STATE_LOADED);
/**
 * Module de snif du navigateur
 * @todo : refondre le système pour abandonner le snif du userAgent au profit de l'existence de méthodes et comportements
 * @todo : se débarrasser de getEngine, getVersion, getRevision, getBuild, getEmulation, mshtml (garder que IS.ie())
 * @todo : commenter correctement
 * @public
 */
AI.Browser = function()
{
  var U = AI.utils;
  var N = navigator;
  var A = N.userAgent;
  var E = null;
  var V = null;
  var M = 0;
  var m = 0;
  var R = 0;
  var B = 0;
  var Z = null;
  var W;

  if ( $W.opera && (/Opera[\s\/]([0-9\.]*)/.test(A)) )
  {
    E = "opera";
    V = RegExp.$1;

    // Fix Opera version to match wikipedia style
    V = V.substring(0, 3) + "." + V.substring(3);

    Z = A.contains("MSIE") ? "mshtml" : A.contains("Mozilla") ? "gecko" : null;
  }
  else if ( typeof N.vendor == 'string' && N.vendor == "KDE" )
  {
    E = "khtml";
  }
  else if ( typeof N.product == 'string' && N.product == "Gecko" && (/rv\:([^\);]+)(\)|;)/.test(A)) )
  {
    E = "gecko";
    V = RegExp.$1;
  }
  else if ( /MSIE\s+([^\);]+)(\)|;)/.test(A) )
  {
    E = "mshtml";
    V = RegExp.$1;
  }

  if ( V )
  {
    W = V.split('.');
    M = W[0] || 0;
    m = W[1] || 0;
    R = W[2] || 0;
    B = W[3] || 0;
  }

  /*
  ---------------------------------------------------------------------------
    PUBLIC HANDLERS
  ---------------------------------------------------------------------------
  */

  this.getEngine = function() { return E; };
  this.getVersion = function() { return V; };
  this.getMajor = function() { return M; };
  this.getMinor = function() { return m; };
  this.getRevision = function() { return R; };
  this.getBuild = function() { return B; };
  this.getEmulation = function() { return Z; };
  this.ie = U.returnFalse;
  this.mshtml = U.returnFalse;
  this.gecko = U.returnFalse;
  this.opera = U.returnFalse;
  this.khtml = U.returnFalse;
  this[E] = U.returnTrue;
  this.strict = U['return' + ( $D.compatMode == 'CSS1Compat' ? 'True':'False')];
  this.quirks = U['return' + ( $D.compatMode == 'CSS1Compat' ? 'False':'True')];
  /* DEPRECATED */
  this.strictMode = this.strict;
  this.quirksMode = this.quirks;
};

var IS = new AI.Browser();
/*@cc_on IS.ie = AI.utils.returnTrue; @*/
/**
 * Basé sur QxExtend.js 1.7.2.110 du projet qooxdoo
 * LGPL 2.1: http://creativecommons.org/licenses/LGPL/2.1/
 * http://qooxdoo.oss.schlund.de
 */
var $P;

/**
 * Héritage d'objet
 * @param {object} S (Superclass) La référence vers l'objet de base
 * @param {string} C (ClassName) Le nom (complete) de la classe héritée, les . sont remplacés par des _
 * @return {object} La référence $P
 * @public
 */
Function.prototype.extend = function(S, C)
{
  var F = new Function();
  F.prototype = S.prototype;
  $P = this.prototype = new F();
  // humm, est-ce que ça sert réellement ça ?
  this.superclass = S;

  $P.classname = this.classname = C.replace(/\./, '_');
  $P.constructor = this;

  return $P;
};

/**
 * Ajout d'un propriété à l'objet
 *
 * Exemple d'utilisation :
 *   MonObjet.addP( { N:'name'[, D:'default'[, T:'type']] } );
 * Types possibles :
 *   int || bool
 *
 * @param {object} p [Properties] Objet contenant les informations de la propriété, les clés sont N (Name), D (Default), T (Type). Seul N est obligatoire
 * @public
 */
Function.prototype.addP = function(p)
{
  // construit shorter prototype access
//  var pp = this.prototype;

  p.M = p.N.toFirstUp();

  if ( !(p.D && AI.utils.isValid(p.D)) )
  {
    p.D = null;
  }

  // upper-case name
  p.up = p.N.toUpperCase();

  var V = '_value' + p.M, /* clé de sauvegarde de la valeur dans l'objet */
      AC = '_autocheck' + p.M; /* clé de l'autochecker */

  // apply default value
  $P[V] = p.D;

  // construit getFoo(): Returns current stored value
  $P['get' + p.M] = function() { return this[V]; };

  // construit forceFoo(): Set (override) without do anything else
  $P['force' + p.M] = function(N) { this[V] = N; return N; };

  // construit resetFoo(): Reset value to default value
  $P['reset' + p.M] = function() { return this['set' + p.M](p.D); };

  // construit autocheck qui est utilisé lorsque le type est spécifié
  // et que _check n'est pas défini pour la propriété
  $P[AC] = function(N) { return N; };

  if ( typeof p.T === 'string' )
  {
    switch ( p.T )
    {
      case 'bool':
        // construit toggleFoo(): Switch les valeurs booleennes
        $P['toggle' + p.M] = function() { return this['set' + p.M](!this[V]); };
        // construit autocheckFoo()
        $P[AC] = function(N) { return N ? true:false; };
        // construit isFoo()
        $P['is' + p.M] = $P['get' + p.M];
      break;
      case 'int':
        // construit autocheckFoo()
        $P[AC] = function(N) { return intval(N); };
      break;
    }
  }
    

  // construit setFoo(): Définition de la nouvelle valeur
  $P['set' + p.M] = function(N)
  {
    var M = '_modify' + p.M,
        C = '_check' + p.M,
        O = this[V];

    // Ne fait rien si la nouvelle valeur est égale à l'ancienne
    if ( N === O ) { return N; }

    // Vérifie et transforme la nouvelle valeur à traver le checker (ou l'autochecker) avant la sauvegarde
    N = this[ typeof this[C] !== 'undefined' ? C : AC](N);

    // Ne fait rien si la nouvelle valeur est égale à l'ancienne
    if ( N === O ) { return N; }

    // Enregistre la nouvelle valeur
    this[V] = N;

    // Vérifie si un modifier est implémenté
    if ( this[M] )
    {
      try { this[M](N, O); }
      catch(x) { this.error('Echec modif ' + p.N + ' (' + x + '); ' + this.toOid() + '.set' + p.M + '(' + N + ', ' + O + ')', M); }
    }

    return N;
  };

};
/**
 * CONSTRUCTEUR
 * @param {boolean} a        true si autodispose, false si dispose manuel
 * @public
 */
AI.obj = function(a)
{
  this.hashCode = AI.obj._hashCode++;
  if ( a )
  {
    AI.obj.cacheInstance(this);
  }

  return this;
};

/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

/*
---------------------------------------------------------------------------
  VARIABLES
---------------------------------------------------------------------------
*/

// compteur interne. Utilisé comme identifiant unique par chaque instance d'objet
AI.obj._hashCode = 0;

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

AI.obj.extend(Object, 'AI.obj');

/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

//
AI.obj.addP({ N:'enabled', T:'bool', D:true });

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/**
 * Retourne le hashCode en le forcant si il n'existe pas
 * @param {object} o   L'objet à partir duquel obtenir le hashcode
 * @return (integer} Le hashcode
 * @public
 */
AI.obj.toHashCode = function(o)
{
  if ( o.hashCode && o.hashCode !== null ) { return o.hashCode; }
  o.hashCode = AI.obj._hashCode++;
  return o.hashCode;
};

/**
 * Retourne la représentation textuelle d'un objet
 * @return {string} Le texte de l'objet
 * @todo : on pourrait pas enlever les parenthèses de la ternaire ?
 * @public
 */
$P.toString = function() { return ( this.classname ) ? "[object " + this.classname + "]" : "[object Object]"; };

/**
 * Retourne le hashCode unique de l'instance
 * @return (integer} Le hashcode
 * @public
 */
$P.toHashCode = function() { return this.hashCode; };

/**
 * Retourne un id interne plus lisible pour les humains que le hashCode
 * @return {string} L'identifiant human readable
 * @public
 */
$P.toOid = function() { return this.classname.replace(/^AI_/, '') + this.toHashCode(); };

/*
---------------------------------------------------------------------------
  INSTANCE CACHE MANAGER
---------------------------------------------------------------------------
*/

// cache des instances
AI.obj._inst = {};

// cache une instance
AI.obj.cacheInstance = function(I)
{
  AI.obj._inst[I.hashCode] = I;
};

// recupère une instance du cache par son hashCode
AI.obj.getCacheInstance = function(h)
{
  if ( AI.obj._inst[h] !== null )
  {
    return AI.obj._inst[h];
  }
  return null;
};

// recupère une instance du cache par son élément DOM
AI.obj.getCacheInstanceFromElement = function(E)
{
  if ( typeof E.hashCode !== 'undefined' )
  {
    return AI.obj.getCacheInstance(E.hashCode);
  }
  return null;
};

/*
---------------------------------------------------------------------------
  USER DATA
---------------------------------------------------------------------------
*/

// Données privées de l'objet
$P._datas = null;

// Retourne les données privées
$P.getDatas = function() { return this._datas; };

// assign les datas sous format JSON
$P.setDatas = function (d)
{
  this._datas = d;
  this._afterSetDatas();
};

// Supprime les données privées de l'objet
$P.removeDatas = function() { this._datas = null; };

// méthode vide, utilisée pour effectuer des traitements supplémentaires lorsque les datas sont assignées
$P._afterSetDatas = AI.utils.returnTrue;

// Construit les données privées de l'objet en les lisant depuis le contenu d'un élément HTML
// Nécessite une surcharge par les objets capables d'effectuer cette opération en fonction des besoins
$P.readDatas = AI.utils.returnTrue;

/*
---------------------------------------------------------------------------
  DEBUGGING INTERFACE
---------------------------------------------------------------------------
*/

// Print out a debug message to the debug console.
$P.debug = function(m, c) { AI_debug(this.classname + '[HASHCODE:' + this.hashCode + ']', m, c); };

// Print out an info message info to the debug console.
$P.info = function(m, c) { this.debug(m, 'info'); };

// Print out an warning to the debug console.
$P.warn = function(m, c) { this.debug(m, 'warning'); };

//Print out an error to the debug console.
$P.error = function(m, f)
{
  if ( AI.utils.isValidString(f) )
  {
    this.debug('Erreur exécution "' + f + '()": ' + m, 'error');
  }
  else
  {
    this.debug(m, 'error');
  }
};


/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.disposed = false;

// Dispose this object
$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  // supprime les datas
  if ( this._datas )
  {
    for ( var p in this._datas )
    {
      delete this._datas[p];
    }

    delete this._datas;
  }

  // supprimer la référence du cache
  if ( this.hashCode && AI.obj._inst[this.hashCode] )
  {
    delete AI.obj._inst[this.hashCode];
  }

  // supprime les variables
  delete this.hashCode;

  // Marque as disposed
  this.disposed = true;
  
  return true;
};

/*
---------------------------------------------------------------------------
  COMPLETE DISPOSER // appelé depuis AI.application
---------------------------------------------------------------------------
*/

AI.obj.dispose = function()
{
  for ( var i = AI.obj._inst.length; i--; )
  {
    var o = AI.obj._inst[i];
    if ( o !== null )
    {
      o.dispose();
      delete AI.obj._inst[i];
    }
  }
  $P = null;
};
/**
 * CONSTRUCTEUR
 * @param {boolean} A [Autodispose] true si autodispose, false si dispose manuel
 * @public
 */
AI.dispatcher = function(A)
{
  // listeners
  this.$L = {};
  return AI.obj.call(this, A);
};

/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

/* NOP */

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

AI.dispatcher.extend(AI.obj, 'AI.dispatcher');

/*
---------------------------------------------------------------------------
  EVENT CONNECTION
---------------------------------------------------------------------------
*/

/**
 * Ajoute un listener à l'objet
 * @param {string}   T [Type]     Type de listener
 * @param {function} F [Fonction] Handler du listener
 * @param {object}   O [scOpe]    Objet d'application du listener (this par défaut)
 * @public
 */
$P.addListener = function(T, F, O)
{
  if ( this.disposed ) { return false; }

  if ( typeof this.$L[T] === 'undefined' )
  {
    this.$L[T] = {};
  }

  // Create a special vKey string to allow identification of each bound action
  var K = AI.dispatcher._boundKey(F, O);

  // Finally set up the listeners object
  this.$L[T][K] =
  {
    f : F,
    o : AI.utils.isValid(O) ? O : this
  };
  return true;
};

/**
 * Supprime un listener de l'objet
 * @param {string}   [T]   Type de listener
 * @param {function} [F]   Handler du listener
 * @param {object}   [O]   Objet d'application du listener (this par défaut)
 * @public
 */
$P.removeListener = function(T, F, O)
{
  var L = this.$L;
  if ( this._disposed  || !L || typeof L[T] === 'undefined' ) { return false; }

  // Create a special vKey string to allow identification of each bound action
  var K = AI.dispatcher._boundKey(F, AI.utils.isValid(O) ? O : this);

  // Delete object entry for this action
  delete this.$L[T][K];
  
  return true;
};

/*
---------------------------------------------------------------------------
  EVENT DISPATCH
---------------------------------------------------------------------------
*/

/**
 * Dispatch un listener
 * @param {string} [T] Type de listener
 * @param {object} [A] Objet supplémentaire fourni au listener
 */
// Internal dispatch implementation
$P.dispatch = function(T, A)
{
  if ( this.disposed ) { return false; }

  this._canBubbleDispatch = true;
  if ( this.hasListeners(T) )
  {
    var L = this.$L;
    if ( L )
    {
      // Shortcut for listener data
      var tL = L[T];

      if ( tL )
      {
        var F, O;

        // Handle all events for the specified type
        for ( var h in tL )
        {
          // Shortcuts for handler and object
          F = tL[h].f;
          O = tL[h].o;

          // Call object function
          try
          {
            if ( typeof F === 'function' )
            {
              F.call(O, T, A);
            }
          }
          catch (x)
          {
            this.error('Impossible de dispatcher "' + T + '": ' + x, 'dispatch');
          }
        }
      }
    }
  }

  // Bubble event to parent
  if ( this._canBubbleDispatch && this.getParent )
  {
    var P = this.getParent();
    if ( this._canBubbleDispatch && P !== null )
    {
      P.dispatch(T, A);
    }
  }
  return true;
};

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

// créé la clé de liaison
AI.dispatcher._boundKey = function(F, o)
{
  return 'evt' + AI.obj.toHashCode(F) + (o ? '_' + AI.obj.toHashCode(o) : '');
};

// Check if there are one or more listeners for an event type
$P.hasListeners = function(T)
{
  return this.$L && typeof this.$L[T] !== 'undefined' && !AI.utils.isObjectEmpty(this.$L[T]);
};

/*
---------------------------------------------------------------------------
  MISCELLEANEOUS
---------------------------------------------------------------------------
*/

// Internal placeholder for bubbling phase of an event.
$P.getParent = AI.utils.returnNull;

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  for ( var T in this.$L)
  {
    for ( var K in this.$L[T] )
    {
      delete this.$L[T][K];
    }
    delete this.$L[T];
  }
  delete this.$L;
  delete this._canBubbleDispatch;

  return AI.obj.prototype.dispose.call(this);
};
/**
 * CONSTRUCTEUR
 * @param {integer} I (Intervalle) Intervalle initiale
 * @public
 */
AI.timer = function(I)
{
  AI.dispatcher.call(this, false);

  this.setEnabled(false);

  if ( AI.utils.isValidNumber(I) )
  {
    this.setInterval(I);
  }

  // Object wrapper to timer event
  var o = this;
  this.$$I = function() { o._onint(); };

  return this;
};

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

AI.timer.extend(AI.dispatcher, "AI.timer");

/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

AI.timer.addP({ N:"interval", T:'int', D:1000 });

/*
---------------------------------------------------------------------------
  MODIFIER
---------------------------------------------------------------------------
*/

// handler d'intervalle
$P.$intH = null;
$P._modifyEnabled = function(V, O)
{
  if ( !this.disposed )
  {
    if ( O )
    {
      $W.clearInterval(this.$intH);
      this.$intH = null;
    }
    else if ( V )
    {
      this.$intH = $W.setInterval(this.$$I, this.getInterval());
    }
  }
};

// humm, je crois qu'il sert pas lui ici
//$P.getId = function() { return this.toHashCode(); };

/*
---------------------------------------------------------------------------
  PUBLIC
---------------------------------------------------------------------------
*/

/**
 * Démarre le timer
 * @public
 */
$P.start = function()
{
  this.setEnabled(true);
};

/**
 * Démarre le timer avec une nouvelle intervalle
 * @param {integer} I (Interval) La valeur de la nouvelle intervalle à appliquer
 * @public
 */
$P.startWith = function(I)
{
  this.setInterval(I);
  this.start();
};

/**
 * Stop le timer
 * @public
 */
$P.stop = function()
{
  this.setEnabled(false);
};

/**
 * Stop et relance le timer
 * @public
 */
$P.restart = function()
{
  this.stop();
  this.start();
};

/**
 * Stop et relance le timer avec une nouvelle intervalle
 * @param {integer} I (Interval) La valeur de la nouvelle intervalle à appliquer
 * @public
 */
$P.restartWith = function(I)
{
  this.stop();
  this.startWith(I);
};

/*
---------------------------------------------------------------------------
  EVENT-MAPPER
---------------------------------------------------------------------------
*/

$P._onint = function()
{
  if ( this.getEnabled() )
  {
    this.dispatch('interval');
  }
};

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  // Stop interval
  this.stop();

  // Clear handle
  if ( this.$intH )
  {
    $W.clearInterval(this.$intH);
    this.$intH = null;
    try { delete this.$intH; } catch(x) {}
  }

  // Clear object wrapper function
  this.$$I = null;
  try { delete this.$$I; } catch(x) {}

  return AI.dispatcher.prototype.dispose.call(this);
};

/**
 * HELPER
 * @param {object}  F (Fonction)   La référence vers la fonction à appeler
 * @param {object}  S (Scope)      Le scope d'application
 * @param {integer} I (Intervalle) Période d'attente en millisecondes
 */
AI.timer.once = function(F, S, I)
{
  var T = new AI.timer(I);
  T.addListener('interval', function(e) { F.call(S, e); T.dispose(); S = T = null; }, S);
  T.start();
};
/**
 * CONSTRUCTEUR
 * @param {boolean} a        true si autodispose, false si dispose manuel
 * @public
 */
AI.parent = function(a)
{
  AI.dispatcher.call(this, a);

  // Contient tous les childrens
  this._children = [];

  this._layoutTimer = new AI.timer(1000);
  this._layoutTimer.addListener('interval', AI.parent._onLayoutTimer, this);

  return this;
};

/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

/* NOP */

/*
---------------------------------------------------------------------------
  EXTEND OBJECT
---------------------------------------------------------------------------
*/

AI.parent.extend(AI.dispatcher, 'AI.parent');

/*
---------------------------------------------------------------------------
  PROPRIETES
---------------------------------------------------------------------------
*/

// Le widget parent (le réel objet, pas un ID ou autre chose)
AI.parent.addP({ N:'parent' });

/*
---------------------------------------------------------------------------
  QUEUE DE TRAITEMENT ([append|remove]Child)
---------------------------------------------------------------------------
*/
/*
AI.parent._queue = [];

AI.parent._addToQueue = function(vParent, vChild, vRemplace)
{
  AI.parent._queue.push({P:vParent, C:vChild, R:vRemplace});
};

AI.parent._flushQueue = function()
{
  var Q = AI.parent._queue;
  var instance_cible, instance_parent, elt;
  for ( var i=0, imax=Q.length; i<imax; i++ )
  {
    var q = Q[i];
    var p = q.P;
    var c = q.C;
    if ( q.R )
    {
      p.parentNode.replaceChild(c, p);
    }
    else
    {
      instance_cible = AI.obj.getCacheInstanceFromElement(c);
      if ( instance_cible && AI.utils.isValidNumber(instance_cible._insertIndex) )
      {
        try
        {
          instance_parent = AI.obj.getCacheInstanceFromElement(p);
          elt = instance_parent.getChildren()[instance_cible._insertIndex].getElement();
          p.insertBefore(c, elt);
        }
        catch(ex)
        {
          p.appendChild(c);
        }
      }
      else
      {
        p.appendChild(c);
      }
    }
  }
  AI.parent._queue = [];
};
*/
/*
---------------------------------------------------------------------------
  MODIFIERS
---------------------------------------------------------------------------
*/

$P._hasParent = false;

$P._modifyParent = function(V, O)
{
  if ( O )
  {
    // Supprime du tableau des childrens
    O.getChildren().removeAt( O.getChildren().indexOf(this) );

    this._hasParent = false;
    
    this._beforeRemoveDom();
    this.getElement().parentNode.removeChild(this.getElement());
    this._afterRemoveDom();
  }

  if ( V )
  {
    this._hasParent = true;

    if ( this._insertIndex && AI.utils.isValidNumber(this._insertIndex) )
    {
      V.getChildren().insertAt(this, this._insertIndex);
      delete this._insertIndex;
    }
    else
    {
      V.getChildren().push(this);
    }

    if ( !this.created )
    {
      this._createElementImpl();
    }

    this._beforeInsertDom();
    if ( this.getIdReplace() )
    {
      var c = $(this.getIdReplace());
      this.$elt.id = this.getIdReplace();
      c.parentNode.replaceChild(this.$elt, c);
//      AI.parent._addToQueue(c, this.$elt, true);
    }
    else
    {
      V.getElement().appendChild(this.getElement());
//      AI.parent._addToQueue(V.getElement(), this.getElement());
    }
    this._afterInsertDom();
  }
  else
  {
    this._hasParent = false;
  }
};

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: FIRST CHILD
---------------------------------------------------------------------------
*/

// Retourne le premier child
$P.getFirstChild = function() { return this.getChildren().getFirst(); };

// Retourne le premier child visible
//$P.getFirstVisibleChild = function() { return this.getVisibleChildren().getFirst(); };

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: LAST CHILD
---------------------------------------------------------------------------
*/

// Retourne le dernier child
$P.getLastChild = function() { return this.getChildren().getLast(); };

// Retourne le dernier child visible
//$P.getLastVisibleChild = function() { return this.getVisibleChildren().getLast(); };

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: LOOP UTILS
---------------------------------------------------------------------------
*/

// Exécute une fonction sur tous les childs
$P.forEachChild = function(F)
{
/*
  var C=this.getChildren(), chc, i=-1;
  while(chc=C[++i])
  {
    F.call(chc, i);
  }
*/

  var C = this.getChildren();
  for ( var i = 0, m = C.length; i < m; i++ )
  {
    F.call(C[i], i);
  }
};

// Exécute une fonction sur tous les childs visibles
//$P.forEachVisibleChild = function(vFunc)
//{
/*
  var ch=this.getVisibleChildren(), chc, i=-1;
  while(chc=ch[++i])
  {
    vFunc.call(chc, i);
  }
*/

//  var ch=this.getVisibleChildren();
//  for (var i=0, ilen=ch.length; i<ilen; i++)
//  {
//    vFunc.call(ch[i], i);
//  }

  /*
   jslint aime pas cette syntaxe chc=ch[++i],
   voir si ca c'est mieux sans casser
  var ch=this.getVisibleChildren();
  for (var i=0, ilen=ch.length; i<ilen; i++)
  {
    vFunc.call(ch[i], ++i);
  }
  */
//};

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT
---------------------------------------------------------------------------
*/

// Retourne le parent de plus haut niveau
// Hummm, ce serait pas *TOUJOURS* window.$$app par hasard ??
$P.getTopLevelWidget = function()
{
  return this._hasParent ? this.getParent().getTopLevelWidget() : null;
};

/**
 * Retourne le parent de plus haut niveau du type "inst"
 * Par exemple, pour qu'un bouton dans une toolbar retrouve son plus haut pere,
 * de type AI.window on fait : (this étant l'instance de AI.button)
 *  this.getParentInstanceOf(AI.window);
 * @todo : ben le faire :)
 * @public
 */
/*
$P.getParentInstanceOf = function(inst)
{
  return null;
};
*/
// Retourne le tableau de tous les childrens
$P.getChildren = function() { return this._children; };

// Retourne le décompte des childrens
$P.getChildrenLength = function() { return this.getChildren().length; };

// Vérifie si le widget à un child
$P.hasChildren = function() { return this.getChildrenLength() > 0; };

// Verifie si il y a des childrens à l'intérieur
$P.isEmpty = function() { return this.getChildrenLength() === 0; };

// Retourne la position du child
$P.indexOf = function(c) { return this.getChildren().indexOf(c); };

// Vérifie si le widget fourni est déjà un child
$P.contains = function(W)
{
  switch( W )
  {
    case null:
      return false;

    case this:
      return true;

    default:
      // try the next parent of the widget (recursive until found)
      return this.contains(W.getParent());
  }
};

// Vérifie si le child fourni est dans la hierarchie ascendante ou descendante
$P.isInHierarchy = function(c)
{
  return this.contains(c) || c.contains(this);
};


/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: MANAGE VISIBLE ONES

  uses a cached private property
---------------------------------------------------------------------------
*/

/*
// Return the array of all visible children
// (which are configured as visible=true)
$P._computeVisibleChildren = function()
{
  var vVisible = [];
  var vChildren = this.getChildren();
  var vLength = vChildren.length;

  for (var i=0; i<vLength; i++)
  {
    var vChild = vChildren[i];
    if ( vChild.seeable )
    {
      vVisible.push(vChild);
    }
  }

  return vVisible;
};

// Get length of visible children
$P.getVisibleChildrenLength = function()
{
  return this.getVisibleChildren().length;
};

// Check if the widget has any visible children
$P.hasVisibleChildren = function()
{
  return this.getVisibleChildrenLength() > 0;
};

// Check if there are any visible childrens inside
$P.isVisibleEmpty = function()
{
  return this.getVisibleChildrenLength() === 0;
};
*/

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: ADD
---------------------------------------------------------------------------
*/

/**
 * Ajoute un widget. Ajout multiple autorisé
 * @public
 */
$P.add = function()
{
  for ( var i = 0, m = arguments.length; i < m; i++ )
  {
    var W = arguments[i];
    W.setParent(this);
  }
  
  this.layoutUpdate();

  return this;
};

/**
 * Ajoute un widget à un index spécifique
 * @param {object}  c [Child] Widget a ajouter
 * @param {integer} i [Index] Position d'insertion
 * @public
 */
$P.addAt = function(c, i)
{
  if ( c.getParent() == this )
  {
    var C = this.getChildren(), /* tous les fils */
        I = C.indexOf(c); /* ancien index du widget à insérer */
    if ( I != i )
    {
      if ( I != -1 ) { C.removeAt(I); }
      C.insertAt(c, i);
    }
  }
  else
  {
    c._insertIndex = i;
    c.setParent(this);
  }

  this.layoutUpdate();
};

/**
 * Ajoute un widget devant tous les autres
 * @param {object}  c [Child] Widget a ajouter
 * @public
 */
$P.addAtBegin = function(c) { return this.addAt(c, 0); };

/**
 * Ajoute un widget après tous les autres
 * we need to fix here, when the child is already inside myself, but
 * want to change its position
 * @param {object}  c [Child] Widget a ajouter
 * @public
 */
$P.addAtEnd = function(c)
{
  var L = this.getChildrenLength();
  return this.addAt(c, c.getParent() == this ? L - 1 : L);
};

/**
 * Ajoute un widget devant un autre widget déjà ajouté
 * @param {object}  c [Child]  Widget a ajouter
 * @param {object}  b [Before] Widget de référence
 * @public
 */
$P.addBefore = function(c, b)
{
  var C = this.getChildren(), /* tous les fils */
      T = C.indexOf(b), /* index du widget de référence */
      S = C.indexOf(c); /* index du widget à insérer */

  if ( T == -1 )
  {
    throw new Error('addBefore(' + c + ', ' + b + '); Ref erreur');
  }


  if ( S == -1 || S > T )
  {
    T++;
  }

  return this.addAt(c, Math.max(0, T - 1));
};

/**
 * Ajoute un widget derrière un autre widget déjà ajouté
 * @param {object}  c [Child]  Widget a ajouter
 * @param {object}  a [After] Widget de référence
 * @public
 */
$P.addAfter = function(v, a)
{
  var C = this.getChildren(), /* tous les fils */
      T = C.indexOf(a), /* index du widget de référence */
      S = C.indexOf(c); /* index du widget à insérer */

  if ( T == -1 )
  {
    throw new Error('addAfter(' + c + ', ' + a + '); Ref erreur');
  }

  if ( S != -1 && S < T )
  {
    T--;
  }

  return this.addAt(c, Math.min(C.length, T + 1));
};

/*
---------------------------------------------------------------------------
  CHILDREN MANAGMENT: REMOVE
---------------------------------------------------------------------------
*/

/**
 * Supprime un ou plusieurs childrens
 * @public
 */
$P.remove = function()
{
  for ( var i = 0, m = arguments.length; i < m; i++ )
  {
    var W = arguments[i];
    if ( W.getParent() == this )
    {
      W.setParent(null);
    }
  }
  this.layoutUpdate();
};

/**
 * Supprime le child à l'index spécifié
 * @param {integer} I [Index] Position de suppression
 * @public
 */
$P.removeAt = function(I)
{
  var c = this.getChildren()[I];
  if ( c )
  {
    delete c._insertIndex;
    c.setParent(null);
  }
  this.layoutUpdate();
};

/**
 * Supprime tous les childrens
 * @public
 */
$P.removeAll = function()
{
  var C = this.getChildren(), /* tous les fils */
      W = C[0]; /* la première référence de widget */

  while ( W )
  {
    this.remove(W);
    W = C[0];
  }
};


/*
---------------------------------------------------------------------------
  REMAPPING
---------------------------------------------------------------------------
*/

/**
 * Remap les méthodes parents vers un nouvel objet
 * @param {object} [T] Cible du remap
 * @public
 */
$P.remapChildrenHandlingTo = function(T)
{
  var t = [ "add", "remove", "addAt", "addAtBegin", "addAtEnd", "removeAt", "addBefore", "addAfter", "removeAll" ];
  this._remap = T;
  for ( var i = 0, m = t.length; i < m; i++ )
  {
    var s = t[i];
    this[s] = new Function("return this._remap." + s + ".apply(this._remap, arguments)");
  }
  delete this._remap;
};

/*
---------------------------------------------------------------------------
  LAYOUT UPDATER
---------------------------------------------------------------------------
*/


/**
 * Dispatch l'event de changement de layout
 * @param {boolean} I [Instant] Instantanné oui/non, true pour un layoutUpdate immédiat, false pour différer de 1000 ms
 * @public
 */
$P.layoutUpdate = function(I)
{
  this._layoutTimer.stop();

  if ( I )
  {
    AI.parent._onLayoutTimer.call(this);
  }
  else
  {
    this._layoutTimer.start();
  }
};

AI.parent._onLayoutTimer = function()
{
  this.dispatch('layoutUpdate');
  this._layoutTimer.stop();
};

/*
---------------------------------------------------------------------------
  DOM SIGNAL HANDLING
---------------------------------------------------------------------------
*/

$P._beforeInsertDom = function() { this.dispatch('beforeInsertDom'); };
$P._afterInsertDom  = function() { this.dispatch('afterInsertDom');  };
$P._beforeRemoveDom = function() { this.dispatch('beforeRemoveDom'); };
$P._afterRemoveDom  = function() { this.dispatch('afterRemoveDom');  };

/*
---------------------------------------------------------------------------
  DISPOSER
---------------------------------------------------------------------------
*/

$P.dispose = function()
{
  if ( this.disposed ) { return true; }

  if ( this._layoutTimer !== null )
  {
    this._layoutTimer.dispose();
  }

  if ( this._children )
  {
    for ( var i = this._children.length; i--; )
    {
      this._children[i].dispose();
      delete this._children[i];
    }
    delete this._children;
  }

  this._remapTarget = null;

  return AI.dispatcher.prototype.dispose.call(this);
};
/*
---------------------------------------------------------------------------
  CONSTRUCTEUR
---------------------------------------------------------------------------
*/
AI.comparaison = {};

/*
---------------------------------------------------------------------------
  HANDLERS
---------------------------------------------------------------------------
*/

AI.comparaison.byString = function(a, b)
{
  return a==b ? 0 : a > b ? 1 : -1;
};

AI.comparaison.byStringCaseInsensitive = function(a, b)
{
  return AI.comparaison.byString(a.toLowerCase(), b.toLowerCase());
};

AI.comparaison.byFloat = function(a, b)
{
  return a - b;
};

AI.comparaison.byInteger = AI.comparaison.byNumber = AI.comparaison.byFloat;

AI.comparaison.byIntegerString = function(a, b)
{
  return parseInt(a, 10) - parseInt(b, 10);
};

AI.comparaison.byFloatString = function(a, b)
{
  return parseFloat(a) - parseFloat(b);
};

AI.comparaison.byNumberString = AI.comparaison.byFloatString;

AI.comparaison.byIPv4 = function(a, b)
{
  var ipa = a.split('.', 4);
  var ipb = b.split('.', 4);

  for (var i=0; i<3; i++)
  {
    a = parseInt(ipa[i], 10);
    b = parseInt(ipb[i], 10);

    if (a != b)
    {
      return a - b;
    }
  }

  return parseInt(ipa[3], 10) - parseInt(ipb[3], 10);
};

AI.comparaison.byZIndex = function(a, b)
{
  return a.getZIndex() - b.getZIndex();
};

// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('core/compare.js', AI.STATE_LOADED);
/*
---------------------------------------------------------------------------
  GLOBAL
---------------------------------------------------------------------------
*/

/**
 * Retourne une référence HTMLElement
 * @param {string} e ID de l'élément dont on veut la référence DOM
 * @public
 * @return {HTMLElement} Une référence DOM vers un élément HTML.
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function $(e, D)
{
//  D = D ? D : $D;
  D = D || $D;
  return typeof e == 'string' ? D.getElementById(e) : e;
}

function $tags(T, D)
{
  D = D || $D;
  return D.getElementsByTagName(T);
}
function $tag(T, i, D) { return $tags(T, D)[i || 0]; }

/**
 * Retourne la valeur ou le texte selectionné d'un <select-simple>
 * @param {string} e ID de l'élément select
 * @param {string} t Type d'extraction (value|text)
 * @private
 * @return {string} La valeur ou le texte sélectionné
 * @see selectedValue, selectedText
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function $$selected(e, t)
{
  var E = $(e);
  if ( E )
  {
    var O = E.options;
    if ( O && O.selectedIndex && O.selectedIndex != -1 )
    {
      return O[O.selectedIndex][t == 'value' ? t : 'text'];
    }
  }
  return '';
}

/**
 * Retourne la valeur sélectionnée d'un <select-simple>
 * @param {String} [e] ID de l'élément select
 * @public
 * @return {String} La valeur selectionnée
 * @see selectedTxt, $$selected
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function selectedValue(e)
{
//  var E = $(e);
//  return E.options[E.options.selectedIndex].value;
  return $$selected(e, 'value');
}

/**
 * Retourne le texte sélectionné d'un <select-simple>
 * @param {String} [e] ID de l'élément select
 * @public
 * @return {String} Le texte selectionné
 * @see selectedValue, $$selected
 * @todo : a déplacer dans util.js ou carrément dans AI.js
 */
function selectedTxt(e)
{
//  var E = $(e);
//  return E.options[E.options.selectedIndex].text;
  return $$selected(e, 'text');
}

/*
---------------------------------------------------------------------------
  MODIFICATION DE L'ELEMENT NODE
---------------------------------------------------------------------------
*/

/**
 * Détermine si un node est dans un autre node
 * @todo S'en débarrasser dans la version publique, je crois pas qu'on s'en serve, mais à garder sous le coude, c'est une belle solution
 * http://www.quirksmode.org/blog/archives/2006/01/contains_for_mo.html
 */
if ( $W.Node && Node.prototype && !Node.prototype.contains )
{
	Node.prototype.contains = function (e)
  {
		return !!(this.compareDocumentPosition(e) & 16);
	};
}

/*
---------------------------------------------------------------------------
  AI.DOM
---------------------------------------------------------------------------
*/

/**
 * Module de gestion du Document Object Model
 * @public
 */
var DOM =
{
  /**
   * Référence vers le tag <head>
   * @public
   */
  head:$D.getElementsByTagName("head")[0],

  /**
   * Cache des éléments créés par document.createElement
   * Le clone d'un élément étant plus rapide que la création d'un nouveau,
   * ce cache permet de stocker les éléments créés afin de ne les créer qu'une
   * seule fois.
   * @private
   */
  $$c:{},

  /**
   * Détermine la position X (left) d'un élément
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La position X de l'élément
   * @see getY
   */
  getX:function(e)
  {
    var E = $(e);
    if ( !E ) { return 0; }
    var C = 0;
    if ( E.offsetParent )
    {
      while ( E.offsetParent )
      {
        C += E.offsetLeft;
        E = E.offsetParent;
      }
    }
/*
    else if ( E.x ) // compatibilité NS4, à dégager
    {
      curleft += obj.x;
    }
*/
    return C;
  },

  /**
   * Détermine la position Y (top) d'un élément
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La position Y de l'élément
   * @see getX
   */
  getY:function(e)
  {
    var E = $(e);
    if ( !E ) { return 0; }
    var C = 0;
    if ( E.offsetParent )
    {
      while ( E.offsetParent )
      {
        C += E.offsetTop;
        E = E.offsetParent;
      }
    }
/*
    else if (obj.y) // compatibilité NS4, à dégager
    {
      curtop += obj.y;
    }
*/
    return C;
  },

  /**
   * Insère un noeud en première position
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string|HTMLElement} p Id du père ou directement sa référence
   */
  insertFirst:function(e, p)
  {
    var E = $(e);
    var P = $(p);
    if ( E && P )
    {
      if ( P.childNodes.length === 0 && P.firstChild )
      {
        P.appendChild(E);
      }
      else
      {
        P.insertBefore(E, P.firstChild);
      }
    }
  },
  
  /**
   * Déplace un noeud devant un autre
   * @param {string|HTMLElement} s Id de l'élément source ou directement sa référence
   * @param {string|HTMLElement} c Id de l'élément cible ou directement sa référence
   * @public
   * @return {HTMLElement} La référence de l'élément déplacé, c'est s
   */
  moveBefore:function(s, c)
  {
    var S = $(s);
    var C = $(c);
    if ( C && S && S.parentNode )
    {
    	var P = S.parentNode;
    	P.removeChild(S);
    	return P.insertBefore(S, C);
    }
    return null;
  },

  /**
   * Déplace un noeud après un autre
   * @param {string|HTMLElement} s Id de l'élément source ou directement sa référence
   * @param {string|HTMLElement} c Id de l'élément cible ou directement sa référence
   * @public
   * @return {HTMLElement} La référence de l'élément déplacé, c'est s
   */
  moveAfter:function(s, c)
  {
    var S = $(s);
    var C = $(c);
    if ( S && S.parentNode )
    {
      var P = S.parentNode;
    	P.removeChild(S);
  	  return P.insertBefore(S, C ? C.nextSibling : null);
   }
   return null;
  },

  /**
   * Créé un noeud texte visuellement vide (IE n'aime pas les élément réellement vides)
   * @public
   * @return {HTMLElement} Un noeud texte vide
   */
  emptyTextNode:function()
  {
    return $dct('\u00a0');
  },

  /**
   * Nettoie les noeuds phantômes du document
   * @param {string|HTMLElement} s Id de l'élément ou directement sa référence
   * @param {boolean} r Recusivité, false par défaut (optional)
   * @public
   * @todo Déplacer dans DOM_whitespace.js
   */
  cleanWhitespace:function(e, r)
  {
    var E = $(e);
    if ( E && E.childNodes )
    {
      for ( var i = E.childNodes.length - 1; i >= 0; i-- )
      {
        var N = E.childNodes[i];
        if ( N.nodeType == $D.TEXT_NODE )
        {
          var V = N.nodeValue.trim();
          if ( V === '' || V == '\n' || V == '\t' || V == '\r' || V == '\r\n' || V == '\n\r' )
          {
            E.removeChild(N);
          }
        }
        else if ( N.nodeType == $D.ELEMENT_NODE && r )
        {
          DOM.cleanWhitespace(N, r);
        }
      }
    }
  },

  /**
   * Retourne le contenu texte de l'élément et de tous ses fils, un peu comme le innerText de IE mais en version DOM
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {string} le contenu textuel de l'élément
   * http://slayeroffice.com/code/functions/so_getText.html
   */
  getInnerText:function(e)
  {
    var E = $(e);
    if ( !E) { return ''; }
  	if ( E.textContent ) { return E.textContent; }
  	if ( E.nodeType == $D.TEXT_NODE ) { return E.data; }
    //var R = [], i = 0;
    var R = [];
    var i = 0;
  	while ( E.childNodes[i] )
    {
  		R.push( DOM.getInnerText(E.childNodes[i]) );
  		i++;
  	}
    return R.join('');
  },

  /**
   * Vide un node de son contenu
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  flush:function(e)
  {
    var E = $(e);
    if ( E )
    {
    	while ( E.firstChild )
      {
        E.removeChild(E.firstChild);
      }
    }
  },

  /**
   * Methode de remplacement pour DOM.flush, Vide un node de son contenu
   * http://slayeroffice.com/test/clone_vs_firstchild.html
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @todo Vérifier si ca marche bien partout et si ca va réellement plus vite
   */
  flushQuick:function(e)
  {
    var E = $(e);
    if ( E )
    {
    	var C = E.cloneNode(false);
    	E.parentNode.insertBefore(C, E);
    	E.parentNode.removeChild(E);
    }
  },

  /**
   * Largeur de la fenetre
   * @public
   * @return {integer} La largeur de la fenetre
   */
  windowHeight:AI.utils.returnZero,

  /**
   * Hauteur de la fenetre
   * @public
   * @return {integer} La hauteur de la fenetre
   */
  windowWidth:AI.utils.returnZero,

  /**
   * Obtient le prochain élément de type n (nodeName) à partir de l'élément e
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             n Le nodeName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément suivant
   */
  suivant:function(e, n)
  {
    // E = Element
    var E = $(e);
    // ajout du lowercase, est-ce que ca a cassé ?
    n = n.toLowerCase();
    if ( !E ) { return null; }
    // S = Suivant
  	var S = E.nextSibling;
  	while ( S !== null )
    {
      // ajout du lowercase, est-ce que ca a cassé ?
  		if ( S.nodeName.toLowerCase() == n )
      {
        return S;
      }
  		S = S.nextSibling;
  	}
  	return null;
  },

  /**
   * Obtient le précédent élément de type n (nodeName) à partir de l'élément e
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             n Le nodeName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément précédent
   */
  precedent:function(e, n)
  {
    // E = Element
    var E = $(e);
    // ajout du lowercase, est-ce que ca a cassé ?
    n = n.toLowerCase();
    if ( !E ) { return null; }
    // P = Precedent
  	var P = E.previousSibling;
  	while ( P !== null )
    {
      // ajout du lowercase, est-ce que ca a cassé ?
  		if ( P.nodeName.toLowerCase() == n )
      {
        return P;
      }
  		P = P.previousSibling;
  	}
  	return null;
  },

  /**
   * Obtient le parent de l'élément e (element) avec le t (tagName) fourni
   * @param {string|HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}             t Le tagName recherché (div, li, td, etc)
   * @public
   * @return {HTMLElement} La référence de l'élément parent du type désiré
   */
  parent:function(e, t)
  {
    var E = $(e);
    if ( !E ) { return null; }
    t = t.toLowerCase();
    while ( E !== null && E.parentNode )
    {
      if ( E.tagName.toLowerCase() == t )
      {
        return E;
      }
      E = E.parentNode;
    }
    return null;
  },

  /**
   * Obtient le nom réel de la propriété CSS adapté au navigateur
   * La version initiale ne fait que retourner le paramètre, elle est ensuite surchargée selon le navigateur
   * @param {string} [N] Nom de la propriété de style dont on veut le nom réel pour le navigateur (version W3C par défaut)
   * @private
   * @return {string} Le nom a utiliser, spécifique au navigateur courant
   */
  _realCss:function(N) { return N; },

  /**
   * Calcul la somme en pixel de tous les arguments (variables)
   * @public
   * @return {string} L'addition entière des arguments suivi par 'px'
   */
  pix:function()
  {
    var p = 0, a = arguments;
    for ( var i = a.length; i--; )
    {
      p += intval(a[i]);
    }
    return p + 'px';
  },

/*
---------------------------------------------------------------------------
  A REFONDRE, A VERIFIER SI UN INTERET EXISTE OU PAS, etc.
---------------------------------------------------------------------------
*/
  /**
   * Obtient les éléments par la class
   * @todo A refaire si on en a vraiment besoin, mais j'en doute
   */
/*
  getElementsByClassName:function(className)
  {
    var children = document.getElementsByTagName('*') || document.all;
    var elements = [];
    for (var i=0, ilen=children.length; i<ilen; i++)
    {
      var child = children[i];
      var classNames = child.className.split(' ');
      for (var j=0, jlen=classNames.length; j<jlen; j++)
      {
        if (classNames[j] == className)
        {
          elements.push(child);
          break;
        }
      }
    }
    return elements;
  },
*/

  /**
   * Obtient les éléments par selector CSS
   * @todo a maintenir avec les dernières versions, si on vient a en avoir besoin
   */
/*
  getElementsBySelector:function(selector) {
    var i;
    var s = [];
    var selid = "";
    var selclass = "";
    var tag = selector;
    var objlist = [];
    if (selector.indexOf(" ")>0) {  //descendant selector like "tag#id tag"
      s = selector.split(" ");
      var fs = s[0].split("#");
      if (fs.length==1) { return objlist; }
      return document.getElementById(fs[1]).getElementsByTagName(s[1]);
    }
    if (selector.indexOf("#")>0) { //id selector like "tag#id"
      s = selector.split("#");
      tag = s[0];
      selid = s[1];
    }
    if (selid!=="") {
      objlist.push($(selid));
      return objlist;
    }
    if (selector.indexOf(".")>0) {  //class selector like "tag.class"
      s = selector.split(".");
      tag = s[0];
      selclass = s[1];
    }
    var v = document.getElementsByTagName(tag);  // tag selector like "tag"
    if (selclass==="") { return v; }
    for (i=0; i<v.length; i++) {
      if (v[i].className==selclass) {
        objlist.push(v[i]);
      }
    }
    return objlist;
  },
*/

/*
  getElementsComputedStyle = function(E, cssProperty, mozillaEquivalentCSS)
  {
    if ( arguments.length == 2 ) { mozillaEquivalentCSS = cssProperty; }
    if ( typeof E == 'string' ) { E = $(E); }
    return (E.currentStyle)? E.currentStyle[cssProperty]:document.defaultView.getComputedStyle(E, null).getPropertyValue(mozillaEquivalentCSS);
  },
*/

  /**
   * Obtient la valeur du style en normalisant currentStyle et ComputedStyle
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {String}               P La propriété recherchée
   * @public
   * @return {String} La valeur current de la propriété de style
   */
  getStyle:function(e, P)
  {
    var E = $(e);
    var V = null;
    var d = $D.defaultView;

    if ( !E ) { return V; }

    // IE opacity, gestion différente
    // todo : appeller DOM.getOpacity() quand P == 'opacity'
    if ( P == 'opacity' && E.filters )
    { 
      V = 1;
      try
      {
        V = E.filters.item('DXImageTransform.Microsoft.Alpha').opacity / 100;
      }
      catch(x)
      {
        try { V = E.filters.item('alpha').opacity / 100; } catch(x) {}
      }
    }
    else if ( E.style[P] )
    {
       V = E.style[P];
    }
    else if ( E.currentStyle && E.currentStyle[P] )
    {
       V = E.currentStyle[P];
    }
    else if ( d && d.getComputedStyle )
    {
      var c = CamelToHyphen(P);

      if ( d.getComputedStyle(E, '').getPropertyValue(c) )
      {
        V = d.getComputedStyle(E, '').getPropertyValue(c);
      }
    }

    return V;
  },

  /**
   * Comme DOM.getStyle(), mais s'assure que le retour est un entier
   *
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {String}               P La propriété recherchée
   * @public
   * @return {String} La valeur current de la propriété de style ou 0
   */
  getStyleInt:function(e, P) { return intval(DOM.getStyle(e, P)) || 0; },

  /**
   * Check si un élément possède une classe
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à vérifier
   * @public
   * @return {boolean} true si l'élément possède la classe
   */
  hasCSS:function(e, C)
  {
    var E = $(e);
    if ( !E || !E.className ) { return false; }
    var c = E.className.split(' ');
    return c.contains(C);
  /*
    for (var i = cls.length; i > 0;)
    {
      if (cls[--i] == className)
      {
        return true;
      }
    }
    return false;
  */
  },

  /**
   * Définit la classe d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à définir
   * @public
   */
  setCSS:function(e, C)
  {
    var E = $(e);
    if ( E )
    {
      E.className = C;
    }
  },

  /**
   * Ajoute une classe à un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à ajouter
   * @public
   */
  addCSS:function(e, C)
  {
    var E = $(e);
    if ( E )
    {
      if ( E.className )
      {
        var c = E.className.split(' ');
        if ( !c.contains(C) )
        {
          c.push(C);
          E.className = c.join(' ');
        }
      }
      else
      {
        E.className = C;
      }
/*
      DOM.removeCSS(E, C);
      if ( E.className )
      {
        E.className += ' ' + C;
      }
      else
      {
        E.className = C;
      }
*/
    }
  },

  /**
   * Supprime une classe d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {string}               C Nom de la classe à supprimer
   * @public
   */
  removeCSS:function(e, C)
  {
    var E = $(e);
    if ( E && E.className )
    {
      var c = E.className.split(' ');
      if ( c.contains(C) )
      {
        c.remove(C);
        E.className = c.join(' ');
      }
/*
      var c = E.className.split(' ');
      var A = [];
      for ( var i = 0, m = c.length; i < m; i++ )
      {
        if ( c[i] != C )
        {
          A[A.length] = c[i];
        }
      }
      E.className = A.join(' ');
*/
    }
  },

  /**
   * Cache un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  hide:function(e) { DOM.addCSS(e, 'cacher'); },

  /**
   * Affiche un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  show:function(e) { DOM.removeCSS(e, 'cacher'); },

  /**
   * Vérifie si l'élément est caché ou pas (ne gère pas la visibilité des parents)
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {boolean} true si l'élément est caché, false dans le cas contraire
   */
  isHide:function(e) { return DOM.hasCSS(e, 'cacher'); },

  /**
   * Toggle l'affichage d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  toggle:function(e) { return DOM.isHide(e) ? DOM.show(e) : DOM.hide(e); },

  /**
   * Toggle l'affichage des childs d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  toggleChilds:function(e)
  {
    var C = $(e).childNodes;
    for ( var i = 0, m = C.length; i < m; i++)
    {
      if ( !DOM.hasCSS(C[i], 'ne_pas_cacher') )
      {
        if ( DOM.isHide(C[i]) )
        {
          DOM.show(C[i]);
        }
        else
        {
          DOM.hide(C[i]);
        }
      }
    }
  },

  /**
   * Obtient la taille de la marge gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge gauche
   */
  getComputedMarginLeft:function(E) { return DOM.getStyleInt(E, 'marginLeft'); },

  /**
   * Obtient la taille de la marge supérieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge supérieure
   */
  getComputedMarginTop:function(E) { return DOM.getStyleInt(E, 'marginTop'); },

  /**
   * Obtient la taille de la marge droite
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge droite
   */
  getComputedMarginRight:function(E) { return DOM.getStyleInt(E, 'marginRight'); },

  /**
   * Obtient la taille de la marge inférieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la marge inférieure
   */
  getComputedMarginBottom:function(E) { return DOM.getStyleInt(E, 'marginBottom'); },

  /**
   * Obtient la taille du padding gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding gauche
   */
  getComputedPaddingLeft:function(E) { return DOM.getStyleInt(E, 'paddingLeft'); },

  /**
   * Obtient la taille du padding supérieur
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding supérieur
   */
  getComputedPaddingTop:function(E) { return DOM.getStyleInt(E, 'paddingTop'); },

  /**
   * Obtient la taille du padding droit
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding droit
   */
  getComputedPaddingRight:function(E) { return DOM.getStyleInt(E, 'paddingRight'); },

  /**
   * Obtient la taille du padding inférieur
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille du padding inférieur
   */
  getComputedPaddingBottom:function(E) { return DOM.getStyleInt(E, 'paddingBottom'); },

  /**
   * Obtient la taille de la bordure gauche
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure gauche
   */
  getComputedBorderLeft:function(E) { return DOM.getStyle(E, "borderLeftStyle")   == 'none' ? 0 : DOM.getStyleInt(E, "borderLeftWidth"); },

  /**
   * Obtient la taille de la bordure supérieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure supérieure
   */
  getComputedBorderTop:function(E) { return DOM.getStyle(E, "borderTopStyle")    == 'none' ? 0 : DOM.getStyleInt(E, "borderTopWidth"); },

  /**
   * Obtient la taille de la bordure droite
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure droite
   */
  getComputedBorderRight:function(E) { return DOM.getStyle(E, "borderRightStyle")  == 'none' ? 0 : DOM.getStyleInt(E, "borderRightWidth"); },

  /**
   * Obtient la taille de la bordure inférieure
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   * @return {integer} La taille de la bordure inférieure
   */
  getComputedBorderBottom:function(E) { return DOM.getStyle(E, "borderBottomStyle") == 'none' ? 0 : DOM.getStyleInt(E, "borderBottomWidth"); },

  /**
   * Applique un style à un élément
   * DOM.applyStyle($('element_id'), 'border:1px solid red; margin:25px;');
   * http://slayeroffice.com/code/functions/so_applyStyleString.html
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  applyStyle:function(e, S)
  {
    var E = $(e);
    if ( E )
    {
      E.setAttribute('style', S);
    }
  },

  /**
   * Définit l'opacité d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @param {float}                V Valeur de l'opacite à appliquer, de 0.00 à 1.00
   * @public
   */
  setOpacity:function(e, V)
  {
    var E = $(e);
    if ( E )
    {
      if ( V === null || V >= 1 || V < 0 )
      {
        DOM.removeOpacity(E);
      }
      else
      {
        E.style.opacity = V;
        DOM._setOpacity(E, V);
      }
    }
  },
  
  /**
   * Définit l'opacité d'un élément spécifiquement par navigateur, surchargé selon le navigateur courant
   * @param {HTMLElement} E La référence de l'élément
   * @param {float}       V Valeur de l'opacite à appliquer, de 0.00 à 1.00
   * @private
   */
  _setOpacity:function(E, V) {},

  /**
   * Supprime l'opacité d'un élément
   * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
   * @public
   */
  removeOpacity:function(e)
  {
    var E = $(e);
    if ( E )
    {
      E.style.opacity = null;
      DOM._removeOpacity(E);
    }
  },

  /**
   * Supprime l'opacité d'un élément spécifiquement par navigateur, surchargé selon le navigateur courant
   * @param {HTMLElement} E La référence de l'élément
   * @private
   */
  _removeOpacity:function(E) {}

};

/*
---------------------------------------------------------------------------
  AI.DOM SPECIFICITES DES NAVIGATEURS
---------------------------------------------------------------------------
*/


if ( self.innerHeight )
{
  DOM.windowHeight = function() { return self.innerHeight; };
  DOM.windowWidth = function() { return self.innerWidth; };
}
else if ( $D.documentElement )
{
  DOM.windowHeight = function() { return $D.documentElement.clientHeight; };
  DOM.windowWidth = function() { return $D.documentElement.clientWidth; };
}
else if ( $D.body )
{
  DOM.windowHeight = function() { return $D.body.clientHeight; };
  DOM.windowWidth = function() { return $D.body.clientWidth; };
}

/*
---------------------------------------------------------------------------
  IE6
---------------------------------------------------------------------------
*/

if ( IS.ie() )
{
  DOM._realCss = function(N)
  {
    if ( N == 'cssFloat' )
    {
      return 'styleFloat';
    }
    return N;
  };

  DOM.applyStyle = function(e, S)
  {
    var E = $(e);
    if ( E )
    {
      E.style.setAttribute('cssText', S);
    }
  };

  DOM._removeOpacity = function(E) { E.style.filter = null; };
  DOM._setOpacity = function(E, V)
  {
    // les filtres ne s'appliquent que sur les éléments qui on hasLayout == true
    // http://www.satzansatz.de/cssd/onhavinglayout.html
    if ( ! ( E.currentStyle && E.currentStyle.hasLayout ) )
    {
      E.style.zoom = 1;
    }
    E.style.filter = 'alpha(opacity=' + Math.round(V * 100) + ')';
    //      E.style.filter = 'alpha(opacity=20)';
  //      E.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + E.src + "',sizingMethod='scale')";
  //      E.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='http://www.internet-ai.com/images/admin/generic/transparent.png',sizingMethod='scale')";

  };
}
/*
---------------------------------------------------------------------------
  GECKO
---------------------------------------------------------------------------
*/
else if ( IS.gecko() )
{
  // Fx < 1.5
  if ( IS.getMajor() <= 1 && IS.getMinor() < 5 )
  {
    DOM._removeOpacity = function(E) { E.style.MozOpacity = null; };
    DOM._setOpacity = function(E, V) { E.style.MozOpacity = V; };
  }
}
/*
---------------------------------------------------------------------------
  KHTML
---------------------------------------------------------------------------
*/
else if ( IS.khtml() )
{
  DOM._removeOpacity = function(E) { E.style.KhtmlOpacity = null; };
  DOM._setOpacity = function(E, V) { E.style.KhtmlOpacity = V; };
}

/*
---------------------------------------------------------------------------
  MODIFICATION DE L'ELEMENT document ($D)
---------------------------------------------------------------------------
*/

/**
 * Copie de la fonction d'origine (document.createElement)
 * @private
 */
$D._createElement = $D.createElement;

/**
 * Copie de la fonction d'origine (document.createDocumentFragment)
 * @private
 */
$D._createDocumentFragment = $D.createDocumentFragment;

/**
 * Fonction de remplacement de document.createElement
 * Permet de de cloner l'élément une fois qu'une version a déjà été créée au lieu
 * d'en créer un nouveau, le clonage étant plus rapide que la création.
 * @param {string} e Le tagName de l'élément à créer
 * @public
 * @return {HTMLElement} L'élément créé
 */
$D.createElement = function(e)
{
	e = e.toLowerCase();

	if ( !DOM.$$c[e] )
  {
		DOM.$$c[e] = $D._createElement(e);
	}

	return DOM.$$c[e].cloneNode(false);
};

/**
 * Fonction de remplacement de document.createDocumentFragment
 * Permet de de cloner l'élément une fois qu'une version a déjà été crée au lieu
 * d'en créer un nouveau, le clonage étant plus rapide que la création.
 * @param {string} e Le tagName de l'élément à créer
 * @public
 * @return {HTMLElement} L'élément créé
 */
$D.createDocumentFragment = function()
{
  if ( !DOM.$$c.$df )
  {
		DOM.$$c.$df = $D._createDocumentFragment();
	}
	return DOM.$$c.$df.cloneNode(false);
};

/*
---------------------------------------------------------------------------
  HELPERS
---------------------------------------------------------------------------
*/

/**
 * Wrapper pour document.createElement
 * @param[in] e {string}      Le nom HTML de l'élément à créer (required)
 * @param[in] o {object}      Hash des différents attributs à appliquer (id, className, onclick, etc.) (optional)
 * @param[in] s {object}      Hash des styles à appliquer (cssFloat, left, marginBottom, etc.) (optional)
 * @param[in] c {array}       Tableau indexé des childs à ajouter (optional)
 * @param[in] t {string}      Mode de manipulation sur le parametre n,  (ac=AppendChild, ib=InsertBefore, if=InsertFirst, rc=ReplaceChild) (optional)
 * @param[in] n {HTMLElement} Node de référence pour le paramètre t (optional)
 * @public
 * @return {HTMLElement} L'élément créé
 */
function $E(e,o,s,c,t,n)
{
  var E = $dce(e);
//  var m = AI.utils.isValidArray(c) ? c.length : 0;
  var m = c && c.length ? c.length : 0;
  if ( o )
  {
    for ( var k in o )
    {
      E[k] = o[k];
    }
  }
  $style(E, s);
  if ( m > 0 )
  {
    for ( var i = 0; i < m; i++ )
    {
      E.appendChild(c[i]);
    }
  }
  if ( AI.utils.isValidString(t) && AI.utils.isValidElement(n) )
  {
    t = t.toLowerCase();
    switch ( t )
    {
      case 'ib':
      case 'before':
      case 'insertbefore':
        n.parentNode.insertBefore(E, n);
      break;
      case 'if':
      case 'first':
      case 'insertfirst':
        DOM.insertFirst(E, n);
      break;
      case 'rc':
      case 'replace':
      case 'replacechild':
        n.parentNode.replaceChild(E, n);
      break;
/*
      case 'ac':
      case 'append':
      case 'appendchild':
*/
      default:
        n.appendChild(E);
      break;
    }
  }
  return E;
}

/**
 * Helper pour manipuler les styles d'un élément
 * @param {string|HTMLElement} e [Element]    Id de l'élément ou directement sa référence
 * @param {object}             P [Properties] Propriétés de style à appliquer
 */
function $style(e, P)
{
  var E = $(e);
  if ( P && E && E.style )
  {
    for ( var k in P )
    {
      if ( k == 'opacity' )
      {
        DOM.setOpacity(E, P[k]);
      }
      else
      {
        E.style[DOM._realCss(k)] = P[k];
      }
    }
  }
}

/*
---------------------------------------------------------------------------
  ALIAS
---------------------------------------------------------------------------
*/
function $dce(e) { return $D.createElement(e); }
function $dct(t) { return $D.createTextNode(t); }

/*
---------------------------------------------------------------------------
  DEPRECATED
---------------------------------------------------------------------------
*/
DOM.findPosX = DOM.getX;
DOM.findPosY = DOM.getY;
/**
 * Sera calculé quand on en aura besoin
 */
DOM.SCROLLBAR_SIZE = null;

DOM.getComputedBoxWidth = function(el)
{
  var h = el.offsetHeight;
  if ( h === 0 )
  {
    var o = el.style.height;
    el.style.height = "1px";
  }

  var v = el.offsetWidth;

  if (h === 0)
  {
    el.style.height = o;
  }

  return v;
};

DOM.getComputedBoxHeight = function(el)
{
  var w = el.offsetWidth;
  if ( w === 0 )
  {
    var o = el.style.width;
    el.style.width = "1px";
  }

  var v = el.offsetHeight;

  if ( w === 0 )
  {
    el.style.width = o;
  }

  return v;
};

if ( IS.gecko() )
{
  DOM.getComputedAreaWidth = function(el)
  {
    // 0 in clientWidth could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    // In Gecko based browsers there is sometimes another
    // behaviour: The clientHeight is equal to the border
    // sum. This is normally not correct and so we
    // fix this value with a more complex calculation.

    // (Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.7.6) Gecko/20050223 Firefox/1.0.1)

    if ( el.clientWidth !== 0 && el.clientWidth != (DOM.getComputedBorderLeft(el) + DOM.getComputedBorderRight(el)) )
    {
      return el.clientWidth;
    }
    else
    {
      return DOM.getComputedBoxWidth(el) - DOM.getComputedInsetLeft(el) - DOM.getComputedInsetRight(el);
    }
  };

  DOM.getComputedAreaHeight = function(el)
  {
    // 0 in clientHeight could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    // In Gecko based browsers there is sometimes another
    // behaviour: The clientHeight is equal to the border
    // sum. This is normally not correct and so we
    // fix this value with a more complex calculation.

    // (Mozilla/5.0 (Windows; U; Windows NT 5.1; de-DE; rv:1.7.6) Gecko/20050223 Firefox/1.0.1)

    if ( el.clientHeight !== 0 && el.clientHeight != (DOM.getComputedBorderTop(el) + DOM.getComputedBorderBottom(el)) )
    {
      return el.clientHeight;
    }
    else
    {
      return DOM.getComputedBoxHeight(el) - DOM.getComputedInsetTop(el) - DOM.getComputedInsetBottom(el);
    }
  };
}
else
{
  DOM.getComputedAreaWidth = function(el)
  {
    // 0 in clientWidth could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    return el.clientWidth !== 0 ? el.clientWidth : (DOM.getComputedBoxWidth(el) - DOM.getComputedInsetLeft(el) - DOM.getComputedInsetRight(el));
  };

  DOM.getComputedAreaHeight = function(el)
  {
    // 0 in clientHeight could mean both: That it is really 0 or
    // that the element is not rendered by the browser and
    // therefore it is 0, too

    return el.clientHeight !== 0 ? el.clientHeight : (DOM.getComputedBoxHeight(el) - DOM.getComputedInsetTop(el) - DOM.getComputedInsetBottom(el));
  };
}

/**
 * Retourne la taille d'une scrollbar
 *
 * @return {Integer} La taille exprimée en pixel
 * @public
 */
DOM.getScrollBarSize = function()
{
  if (DOM.SCROLLBAR_SIZE) { return DOM.SCROLLBAR_SIZE; }

  var div = document.createElement('div');
  div.style.height = '100px';
  div.style.width = "100px";
  div.style.overflow = "scroll";

  document.body.appendChild(div);
  var c = DOM.getComputedScrollBarSizeRight(div);
  if (c)
  {
    DOM.SCROLLBAR_SIZE = c;
  }
  document.body.removeChild(div);
  return c;
};

// Insets
if (IS.ie())
{
  DOM.getComputedInsetLeft   = function(el) { return el.clientLeft; };
  DOM.getComputedInsetTop    = function(el) { return el.clientTop; };
  DOM.getComputedInsetRight  = function(el)
  {
    // DOM.getComputedStyleProperty en DOM.getStyle, je pense que c'est la même fonction, mais à vérifier quand même
    if (DOM.getStyle(el, "overflowY") == 'hidden' || el.clientWidth === 0)
    {
      return DOM.getComputedBorderRight(el);
    }

    return Math.max(0, el.offsetWidth - el.clientLeft - el.clientWidth);
  };

  DOM.getComputedInsetBottom = function(el)
  {
    if (DOM.getStyle(el, "overflowX") == 'hidden' || el.clientHeight === 0)
    {
      return DOM.getComputedBorderBottom(el);
    }

    return Math.max(0, el.offsetHeight - el.clientTop - el.clientHeight);
  };
}
else
{
  DOM.getComputedInsetLeft   = function(el) { return DOM.getComputedBorderLeft(el); };
  DOM.getComputedInsetTop    = function(el) { return DOM.getComputedBorderTop(el); };

  DOM.getComputedInsetRight  = function(el)
  {
    // Alternative method if clientWidth is unavailable
    // clientWidth == 0 could mean both: unavailable or really 0
    if (el.clientWidth === 0)
    {
      var ov = DOM.getStyle(el, 'overflow');
      var sbv = ov == "scroll" || ov == "-moz-scrollbars-vertical" ? 16 : 0;
      return Math.max(0, DOM.getComputedBorderRight(el) + sbv);
    }

    return Math.max(0, el.offsetWidth - el.clientWidth - DOM.getComputedBorderLeft(el));
  };

  DOM.getComputedInsetBottom = function(el)
  {
    // Alternative method if clientHeight is unavailable
    // clientHeight == 0 could mean both: unavailable or really 0
    if (el.clientHeight === 0)
    {
      var ov = DOM.getStyle(el, 'overflow');
      var sbv = ov == "scroll" || ov == "-moz-scrollbars-horizontal" ? 16 : 0;
      return Math.max(0, DOM.getComputedBorderBottom(el) + sbv);
    }

    return Math.max(0, el.offsetHeight - el.clientHeight - DOM.getComputedBorderTop(el));
  };
}

// Scrollbar
DOM.getComputedScrollBarSizeLeft   = function(el) { return 0; };
DOM.getComputedScrollBarSizeTop    = function(el) { return 0; };
DOM.getComputedScrollBarSizeRight  = function(el) { return DOM.getComputedInsetRight(el)  - DOM.getComputedBorderRight(el); };
DOM.getComputedScrollBarSizeBottom = function(el) { return DOM.getComputedInsetBottom(el) - DOM.getComputedBorderBottom(el); };

DOM.getScrollLeftSum = function(el)
{
  var sum = 0;
  var p = el.parentNode;
  while ( p && p.nodeType && p.nodeType == document.ELEMENT_NODE )
  {
    sum += p.scrollLeft;
    p = p.parentNode;
  }
  return sum;
};

DOM.getScrollTopSum = function(el)
{
  var sum = 0;
  var p = el.parentNode;
  while ( p && p.nodeType && p.nodeType == document.ELEMENT_NODE )
  {
    sum += p.scrollTop;
    p = p.parentNode;
  }
  return sum;
};

if ( IS.ie() )
{
  DOM.getComputedClientBoxLeft = function(el) { return el.getBoundingClientRect().left; };
  DOM.getComputedClientBoxTop  = function(el) { return el.getBoundingClientRect().top; };

  DOM.getComputedPageBoxLeft = function(el) { return DOM.getComputedClientBoxLeft(el) + DOM.getScrollLeftSum(el); };
  DOM.getComputedPageBoxTop  = function(el) { return DOM.getComputedClientBoxTop(el)  + DOM.getScrollTopSum(el); };
}
else if ( IS.gecko() )
{
  DOM.getComputedClientBoxLeft = function(el) { return DOM.getComputedClientAreaLeft(el) - DOM.getComputedBorderLeft(el); };
  DOM.getComputedClientBoxTop  = function(el) { return DOM.getComputedClientAreaTop(el)  - DOM.getComputedBorderTop(el); };

  DOM.getComputedPageBoxLeft = function(el) { return DOM.getComputedPageAreaLeft(el) - DOM.getComputedBorderLeft(el); };
  DOM.getComputedPageBoxTop  = function(el) { return DOM.getComputedPageAreaTop(el)  - DOM.getComputedBorderTop(el); };
}
else
{
  DOM.getComputedPageBoxLeft = function(el)
  {
    var sum = 0;
    while ( el && el.tagName.toLowerCase() != 'body' )
    {
      sum += intval(el.offsetLeft);
      el = el.offsetParent;
    }
    return sum;
  };

  DOM.getComputedPageBoxTop = function(el)
  {
    var sum = 0;
    while ( el && el.tagName.toLowerCase() !== 'body' )
    {
      sum += intval(el.offsetTop);
      el = el.offsetParent;
    }
    return sum;
  };

  DOM.getComputedClientBoxLeft = function(el)
  {
    var sum = el.offsetLeft;
    while (el.tagName.toLowerCase() !== 'body')
    {
      el = el.offsetParent;
      if ( el )
      {
        sum += el.offsetLeft - el.scrollLeft;
      }
    }
    return sum;
  };

  DOM.getComputedClientBoxTop = function(el)
  {
    var sum = el.offsetTop;
    while (el.tagName.toLowerCase() !== 'body')
    {
      el = el.offsetParent;
      if ( el )
      {
        sum += el.offsetTop - el.scrollTop;
      }
    }
    return sum;
  };
}

if ( IS.ie() )
{
  DOM.getComputedClientBoxRight  = function(el) { return el.getBoundingClientRect().right; };
  DOM.getComputedClientBoxBottom = function(el) { return el.getBoundingClientRect().bottom; };
  DOM.getComputedPageBoxRight    = function(el) { return DOM.getComputedClientBoxRight(el)  + DOM.getScrollLeftSum(el); };
  DOM.getComputedPageBoxBottom   = function(el) { return DOM.getComputedClientBoxBottom(el) + DOM.getScrollTopSum(el);  };
}
else
{
  DOM.getComputedClientBoxRight  = function(el) { return DOM.getComputedClientBoxLeft(el) + DOM.getComputedBoxWidth(el); };
  DOM.getComputedClientBoxBottom = function(el) { return DOM.getComputedClientBoxTop(el)  + DOM.getComputedBoxHeight(el); };
  DOM.getComputedPageBoxRight    = function(el) { return DOM.getComputedPageBoxLeft(el)   + DOM.getComputedBoxWidth(el); };
  DOM.getComputedPageBoxBottom   = function(el) { return DOM.getComputedPageBoxTop(el)    + DOM.getComputedBoxHeight(el); };
}

if ( IS.gecko() )
{
  DOM.getComputedPageAreaLeft = function(el) { return el.ownerDocument.getBoxObjectFor(el).x; };
  DOM.getComputedPageAreaTop = function(el) { return el.ownerDocument.getBoxObjectFor(el).y; };
  DOM.getComputedClientAreaLeft = function(el) { return DOM.getComputedPageAreaLeft(el) - DOM.getScrollLeftSum(el); };
  DOM.getComputedClientAreaTop = function(el) { return DOM.getComputedPageAreaTop(el) - DOM.getScrollTopSum(el); };
}
else
{
  DOM.getComputedClientAreaLeft = function(el) { return DOM.getComputedClientBoxLeft(el) + DOM.getComputedBorderLeft(el); };
  DOM.getComputedClientAreaTop  = function(el) { return DOM.getComputedClientBoxTop(el)  + DOM.getComputedBorderTop(el); };
  DOM.getComputedPageAreaLeft = function(el) { return DOM.getComputedPageBoxLeft(el) + DOM.getComputedBorderLeft(el); };
  DOM.getComputedPageAreaTop  = function(el) { return DOM.getComputedPageBoxTop(el)  + DOM.getComputedBorderTop(el); };
}

DOM.getComputedClientAreaRight  = function(el) { return DOM.getComputedClientAreaLeft(el) + DOM.getComputedAreaWidth(el);  };
DOM.getComputedClientAreaBottom = function(el) { return DOM.getComputedClientAreaTop(el)  + DOM.getComputedAreaHeight(el); };
DOM.getComputedPageAreaRight  = function(el) { return DOM.getComputedPageAreaLeft(el) + DOM.getComputedAreaWidth(el);  };
DOM.getComputedPageAreaBottom = function(el) { return DOM.getComputedPageAreaTop(el)  + DOM.getComputedAreaHeight(el); };

// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('core/DOM_dimension.js', AI.STATE_LOADED);
/**
 * Tableau des CSS loadées
 * @private
 */
DOM._loaded = {};
/**
 * Identifiant interne
 * on peut pas utiliser le length du tableau puisque on peut les supprimer
 * @private
 */
DOM.$id = 0; 
/**
 * Charge une feuille de style
 * @param {string} U L'url a charger
 * @param {string} I L'identifiant (optionnel)
 * @public
 */
DOM.loadStylesheet = function(U, I)
{
//  if (typeof DOM._loaded[U] === 'undefined')
  if ( !DOM._loaded[U] )
  {
    if (!AI.utils.isValidString(I))
    {
      I = U.replace(/[^\w]/g, '-').toCamelCase() + DOM.$id++;
    }
    var H = document.getElementsByTagName("head")[0];
    var L = document.createElement("link");
    L.id = I;
    L.rel = "stylesheet";
    // version normale
    L.href = U;
    // version forçant la sortie du cache
//    L.href = U + '?' + I;
    H.appendChild(L);
    DOM._loaded[U] = I;
  }
};

/**
 * Supprime la feuille de style
 * @param {string} U L'url a charger
 * @public
 */
DOM.removeStylesheet = function(U)
{
//  if (typeof DOM._loaded[U] !== 'undefined')
  if ( DOM._loaded[U] )
  {
    try
    {
      var H = document.getElementsByTagName("head")[0];
      var L = $(DOM._loaded[U]);
      H.removeChild(L);
    } catch(x) {}
    delete DOM._loaded[U];
  }
};


if ( IS.ie() )
{
  DOM.createStyleElement = function(C)
  {
    var S = document.createStyleSheet();
    if ( C )
    {
      S.cssText = C;
    }
    return S;
  };

  DOM.addCssRule = function(vSheet, vSelector, vStyle)
  {
    vSheet.addRule(vSelector, vStyle);
  };

  DOM.removeCssRule = function(vSheet, vSelector)
  {
    var vRules = vSheet.rules;
    for (var i=vRules.length; i--;)
    {
      if (vRules[i].selectorText == vSelector)
      {
        vSheet.removeRule(i);
      }
    }
  };

  DOM.removeAllCssRules = function(S)
  {
    var R = S.rules;
    for ( var i = R.length; i--; )
    {
      S.removeRule(i);
    }
  };
}
else
{
  DOM.createStyleElement = function(C)
  {
    var E = document.createElement("style");
    E.type = "text/css";
    if ( C )
    {
      E.appendChild(document.createTextNode(C));
    }
    document.getElementsByTagName("head")[0].appendChild(E);
    return E.sheet;
  };

  DOM.addCssRule = function(S, r, t)
  {
    S.insertRule(r + "{" + t + "}", S.cssRules.length);
  };

  DOM.removeCssRule = function(S, r)
  {
    var R = S.cssRules;
    for ( var i = R.length; i--; )
    {
      if ( R[i].selectorText == r)
      {
        S.deleteRule(i);
      }
    }
  };

  DOM.removeAllCssRules = function(S)
  {
    var R = S.cssRules;
    for ( var i = R.length; i--; )
    {
      S.deleteRule(i);
    }
  };
}
/*
---------------------------------------------------------------------------
  Error Extender
---------------------------------------------------------------------------
*/

/**
 * Méthode toString() appliqué à l'objet Error
 * @public
 */
Error.prototype.toString = function() { return this.message; };

/**
 * Surcharge de la méthode toString() si lineNumber et fileName sont accessibles
 * @public
 */
if ( typeof Error.prototype.lineNumber !== 'undefined' && typeof Error.prototype.fileName !== 'undefined' )
{ 
  Error.prototype.toString = function()
  {
    var X = this.fileName.split('/');
    return '#' + this.lineNumber + ':' + X[X.length - 1] + ' "' + this.message + '"';
  };
}

/*
---------------------------------------------------------------------------
  CONSTRUCTEUR
---------------------------------------------------------------------------
*/

function AI_debug(group, message, classname)
{
  var W;
  function CLK(t)
  {
    alerter(t);
/*
    switch ( t )
    {
      case
    }
*/
    return false;
  }
  
  // Check if frame is ready
  if ( !AI_debug._container )
  {
    AI_debug._log = $E('ul', {id:'debug_console'}, {textAlign:'left'});
    AI_debug._container = $E(
      'div', { id:'debug_container', className:'cacher' }, {},
      [
        $E(
          'div', { id:'debug_boutons' }, {},
          [
/*
            $E(
              'span',
              {
                title:'Afficher tous les messages',
                className:'actif',
                onclick:function (e) { return CLK('T'); }
              },
              {},
              [$dct('Tout')]
            ),
            $E(
              'span',
              {
                title:'Afficher les erreurs',
                onclick:function (e) { return CLK('E'); }
              },
              {},
              [$dct('Erreurs')]
            ),
            $E(
              'span',
              {
                title:'Afficher les avertissements',
                onclick:function (e) { return CLK('W'); }
              },
              {},
              [$dct('Avertissements')]
            ),
            $E(
              'span',
              {
                title:'Afficher les messages',
                onclick:function (e) { return CLK('I'); }
              },
              {},
              [$dct('Messages')]
            ),
            $E(
              'span',
              {
                title:'Tout effacer',
                onclick:function (e) { return false; }
              },
              {},
              [$dct('Effacer')]
            )
*/
            $E(
              'span',
              {
                title:'Fermer',
                onclick:function (e) { DOM.hide('debug_container'); return false; }
              },
              {},
              [$dct('Fermer')]
            )
          ]
        ),
        AI_debug._log
      ]
    );
  }

  if ( AI_debug.actif )
  {
    if ( group != AI_debug.lastgroup )
    {
      $E('li', {className:'debug-group'},{},[$dct(group)], 'if', AI_debug._log);
      AI_debug.lastgroup = group;
    }
  
    if ( AI.utils.isValid(message) )
    {
      if ( !AI.utils.isValidString(classname) )
      {
        classname = 'default';
      }
  
      $E('li', {className:'debug-message-' + classname},{},[$dct((new Date().toLocaleTimeString()) + ' ' + message)], 'if', AI_debug._log);
      if ( classname == 'error' )
      {
        AI_debug._container.className = '';
      }
    }
  
    // Check if document is ready
    if ( !AI_debug._container.parentNode || typeof AI_debug._container.parentNode.tagName == 'undefined' )
    {
      if ( $D.body )
      {
        $D.body.appendChild(AI_debug._container);
      }
    }
  }
}

/*
---------------------------------------------------------------------------
  VARIABLES
---------------------------------------------------------------------------
*/

AI_debug.lastgroup = null;
AI_debug.actif = true;

/**
 * gestionnaire global onerror (non implémenté dans opera)
 * @param {string}  [M] Le message d'erreur
 * @param {string}  [U] L'url de l'erreur
 * @param {integer} [L] Ligne ou s'est produite l'erreur
 * @private
 */
$W.onerror = function(M, U, L)
{
  alertError('Erreur : ' + M + CR + 'ligne : ' + L + CR + 'url : ' + U);
/*
  // @todo : mettre en production le log des erreurs
  var i = new Image();
  i.src = '/js/ai/logerror.php?msg=' + encodeURIComponent(msg) + '&url=' + encodeURIComponent(url) + '&lno=' + encodeURIComponent(lno);
  i = null;
*/
  return false;
};

/*
---------------------------------------------------------------------------
  MISCELLEANEOUS
---------------------------------------------------------------------------
*/

/**
 * Synchronization de la gestion des erreurs à travers les fenêtres (top, parent, opener)
 * @public
 * @return {boolean} true si la synchro a eu lieu, false dans le cas contraire
 */
AI._syncDEBUG = function()
{
  var A = ['top', 'parent', 'opener'], /* les scopes de recherche */
      o = null, /* le scope finalement utilisé */
      L = $W.location.hostname; /* le hostname de l'URL en cours */
  for ( var i = 0; i < A.length; i++ )
  {
    o = $W[A[i]];
    try
    {
      if ( o !== self && typeof o != 'undefined' && o !== null && o && o.location && o.location.hostname === L && o.AI && o.AI_debug )
      {
        AI_debug = o.AI_debug;
        return true;
      }
    } catch (x) {}
  }
  return false;
};

if ( !AI._syncDEBUG() )
{
  AI_debug('Initialisation', 'Debugger activé', 'info');
  DOM.loadStylesheet('/css/widget/debug.css', 'debug');
}

/**
 * Toggle l'affichage de la fenêtre de debug
 * @public
 */
AI._GLOBALS.toggleDebug = function()
{
  DOM.toggle('debug_container');
  return false;
};

/**
 * Déclenche une erreur, méthode surchargée au fur et à mesure que le système se met en place
 * @param {string} [M] Message a afficher
 * @private
 */
AI._throwError = function(M)
{
  AI_debug('Erreur JS', M, 'error');
  throw new Error(M);
};
/**
 * Bibliothèque de gestion événements (events)
 *
 * La bibliothèque fournie des méthodes pour ajouter et supprimer des listeners d'events.
 * Elle essaye également de supprimer automatiquement sur 'onunload' les listeners qui
 * n'auraient pas été manuellement supprimé du document.
 *  - remplace le mot clé "this" dans son contexte
 *  - découple les fonctions pour éviter les leaks
 *  - permet de fournir un objet optionnel en paramètre à la méthode callback
 *  - permet de changer le scope de "this" sur l'objet personnalisé
 * voir :
 *  - http://www.quirksmode.org/blog/archives/2005/09/addevent_recodi.html
 *  - http://www.quirksmode.org/blog/index.html
 *  - http://developer.yahoo.net/yui/event/index.html
 *  - http://ejohn.org/projects/flexible-javascript-events/
 *  - http://simon.incutio.com/archive/2004/05/26/addLoadEvent
 *  - http://novemberborn.net/javascript/event-cache/follow-up
 *  - http://www.schillmania.com/script/addeventhandler.js
 *  - http://www.dithered.com/javascript/dom2_events/index.html
 *  - http://www.dithered.com/javascript/dom2_events/dom2_events.txt
 *
 * @type object
 * @constructor
 * @public
 */

var EVT =
{
/*
---------------------------------------------------------------------------
  CONSTANTES
---------------------------------------------------------------------------
*/

msg_unload:__('DECHARGEMENT EN COURS'),

/**
 * Correspondances des touches courantes
 *
 * @type object
 * @public
 * @todo rendre plus lisible
 * @todo manque des correspondances
 * @todo _0, _1, .., _9 n'est pas très parlant
 */
touches :
{
  esc: 27, enter: 13, tab: 9, space: 32,
  up: 38, down: 40, left: 37, right: 39,
  shift: 16, ctrl: 17, alt: 18,
  f1: 112, f2: 113, f3: 114, f4: 115, f5: 116, f6: 117, f7: 118, f8: 119, f9: 120, f10: 121, f11: 122, f12: 123,
  del: 46, backspace: 8, insert: 45, home: 36, end: 35,
  pageup: 33, pagedown: 34,
  numlock: 144,
  numpad_0: 96, numpad_1: 97, numpad_2: 98, numpad_3: 99, numpad_4: 100, numpad_5: 101, numpad_6: 102, numpad_7: 103, numpad_8: 104,  numpad_9: 105,
  numpad_divide: 111, numpad_multiply: 106, numpad_minus: 109, numpad_plus: 107,
  _0: 48, _1: 49, _2: 50, _3: 51, _4: 52, _5: 53, _6: 54, _7: 55, _8: 56, _9: 57
},

/*
---------------------------------------------------------------------------
  VARIABLES
---------------------------------------------------------------------------
*/

/**
 * Derniere position X détectée
 * @public
 */
X : 0,
/**
 * Derniere position X détectée
 * @public
 */
Y : 0,
/**
 * Cache des listeners
 *
 * @type array
 * @private
 */
L : [],

/**
 * Cache des handlers d'event DOM0
 *
 * @type array
 * @private
 */
_DOM0 : [],

/**
 * Listener pour les events DOM0
 *
 * @type array
 * @private
 */
_DOM0H : [],

/**
 * Flag indiquant si le processus de chargement peut continuer
 */
_keep_loading : true,

/**
 * tableau des scripts à lancer sur le onload
 */
$L : [],

/**
 * tableau des scripts à lancer sur le onunload
 */
$U : [],

/*
---------------------------------------------------------------------------
  HANDLERS
---------------------------------------------------------------------------
*/

/**
 * Fixe l'event fournit comme premier paramètre au listener
 * IE ne fournit pas cet objet, il le place dans window.event
 *
 * @param {Event} evt L'event ou null si IE
 * @return {Event} L'event
 * @private
 */
// http://www.outofhanwell.com/blog/index.php?title=cross_window_events&more=1&c=1&tb=1&pb=1
//fix : function(e) { return event || ((this.ownerDocument || this.document || this).parentWindow || window).event;},
fix : function(e) { return e || $W.event; },

/**
 * Ajoute un gestionnaire d'event à un élément
 *
 * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
 * @param {String}               T Le type d'event à ajouter
 * @param {Function}             F La fonction que l'event appelle
 * @param {Object}               S Scope d'exécution
 * @param {Object}               B Un objet qui sera passé comme paramètre au gestionnaire
 * @param {boolean}              D true si le modele DOM0 est imposé
 * @return {boolean} true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
 * @public
 */
add : function(e, T, F, S, B, D)
{
//  if ( typeof E === 'string' ) { E = document.getElementById(E); }
  var E = $(e);
  if ( !E ) { return false; }

  S = S ? S : E;

  var W = function(z) { return F.call(S, EVT.fix(z), B); };

  var L = [E, T, F, W, S];
  var i = EVT.L.length;
  // cache the listener so we can try to automatically unload
  EVT.L[i] = L;

  // Attache l'event à l'objet
  if ( D || EVT._useDOM0(E, T) )
  {
    var I = EVT._getDOM0Index(E, T);
    if ( I == -1 )
    {
      I = EVT._DOM0.length;
      // cache the signature for the DOM0 event
      EVT._DOM0[I] = [E, T, E['on' + T]];
      EVT._DOM0H[I] = [];

      E['on' + T] = function(z) { EVT._fireDOM0(EVT.fix(z), I); };
    }

    // add a reference to the wrapped listener to
    // the custom stack of events
    EVT._DOM0H[I].push(i);
  }
  else
  {
    EVT.bind(E, T, W);
  }

  return true;
},

/**
 * Supprime un gestionnaire d'event
 *
 * @param {string | HTMLElement} e Id de l'élément ou directement sa référence
 * @param {String}               T Le type d'event à supprimer
 * @param {Function}             F La fonction que l'event appelle
 * @public
 */
remove : function(e, T, F)
{
  var E = $(e);
  if ( !E ) { return false; }

  var c = null;
  var i = EVT._getCacheIndex(E, T, F);
  if ( i >= 0 )
  {
    c = EVT.L[i];
  }
  if ( !c ) { return false; }

  var r = true;
  try
  {
    EVT.unbind(E, T, c[3]);
  }
  catch(x)
  {
    alertError('Erreur suppression event : "' + T + '"\nExc: ' + x + '\nElt: ' + E + ( E.tagName ? E.tagName : '' ) + '\nFn : ' + c[3]);
    r = false;
  }
  // removed the wrapped handler
  delete EVT.L[i][3];
  delete EVT.L[i][2];
  delete EVT.L[i];

  return true;
},

/**
 * Annule complètement un event
 *
 * @param {Event} e L'event
 * @public
 */
stop : function(e)
{
  EVT.stopPropagation(e);
  EVT.preventDefault(e);
},

/**
 * Retourne la cible "related" à l'event
 *
 * @param {Event} e L'event
 * @return {HTMLElement} La relatedTarget de l'event
 * @public
 */
getRelatedTarget : function(e)
{
  var t = e.relatedTarget;
  if ( !t )
  {
    if (e.type == "mouseout")
    {
      t = e.toElement;
    }
    else if ( e.type == "mouseover" )
    {
      t = e.fromElement;
    }
  }
  return t;
},

/**
 * Retourne la position X de l'event
 *
 * @param {Event} e L'event
 * @return {int} La position X
 */
getX : function(e)
{
  var x = e.pageX;
  if ( !x && 0 !== x )
  {
    x = e.clientX || 0;
    x += EVT.getScroll('Left');
  }
  EVT.X = x;
  return x;
},

/**
 * Retourne la position Y de l'event
 *
 * @param {Event} e L'event
 * @return {int} La position Y
 */
getY : function(e)
{
  var y = e.pageY;
  if ( !y && 0 !== y )
  {
    y = e.clientY || 0;
    y += EVT.getScroll('Top');
  }
  EVT.Y = y;
  return y;
},


/*
---------------------------------------------------------------------------
  MODELE TRADITIONNEL DOM0
---------------------------------------------------------------------------
*/

/**
 * Routine qui détermine si on doit utiliser le modèle traditionnel ou le model DOM2
 *
 * @param {Object}   E         L'élément html
 * @param {String}   T         Le type d'event
 * @return {boolean} true si le modèle traditionnel doit être utilisé, false si on utilise le modèle DOM2
 * @private
 */
_useDOM0 : function(E, T)
{
  return ( ( !E.addEventListener && !E.attachEvent ) || ( T == "click" && navigator.userAgent.match(/safari/gi) ) );
},

/**
 * Retourne l'index de l'event qui correspond à la signature E + T fournie
 *
 * @param {Object}   E         L'élément html
 * @param {String}   T         Le type d'event
 * @return {int} La position enregistrée
 * @private
 */
_getDOM0Index : function(E, T)
{
  for ( var i = 0, m = EVT._DOM0.length; i < m; i++ )
  {
    var L = EVT._DOM0[i];
    if ( L && L[0] == E && L[1] == T )
    {
      return i;
    }
  }
  return -1;
},

/**
 * Lorsque le modèle traditionnel est utilisé, la routine est routée sur cette
 * fonction qui nous permet de déclencher notrz liste d'events associés
 *
 * @param {Event} e            L'event généré
 * @param {int}   X            La position
 * @return {int} true si l'action a réussi, false si un listener n'a pas pu etre déclenché
 * @private
 */
_fireDOM0 : function(e, X)
{
  var K = true;

  var L = EVT._DOM0H[X];
  for ( var i = 0, m = L.length; i < m; i++ )
  {
    var I = L[i];
    if ( I )
    {
      var l = EVT.L[I];
      var R = l[3].call(l[4], e);
      K = ( K && R );
    }
  }
  return K;
},

/*
---------------------------------------------------------------------------
  UTILITIES
---------------------------------------------------------------------------
*/

/**
 * Retourne la valeur scrollTop ou scrollLeft.
 * Utilisé pour calculer getX et getY
 * traitement spécial pour IE
 */
getScroll : AI.utils.returnZero,

/**
 * Retourne le charcode pour un event
 *
 * @param {Event} e L'event
 * @return {int} Le charcode de l'event
 * @public
 */
getCharCode : function(e)
{
  return e.charCode || (e.type == "keypress") ? e.keyCode : 0;
},

/**
 * Trouve la position de l'event enregistré dans le cache
 *
 * @structure var li = [el, evName, fn, wrappedFn, scope];
 * @private
*/
_getCacheIndex : function(E, T, F)
{
  for ( var i=0, m = EVT.L.length; i < m; i++ )
  {
    var L = EVT.L[i];
    if ( L && L[0] == E && L[1] == T && L[2] == F)
    {
      return i;
    }
  }
  return -1;
},

/**
 * méthode unique appellée sur le onload
 */
_onLoad : function()
{
  var L = EVT.$L;
  for ( var i = 0, m = L.length; i < m; i++ )
  {
    try
    {
      L[i]();
    }
    catch(x)
    {
      var t = 'Erreur lors du chargement' + CR + x;
      try { alertError(t); } catch (y) { $W.alert(t); }
    }
  }
  if ( $W.$$app && typeof $W.$$app != 'undefined' )
  {
    $W.$$app.dispatch('fully_loaded');
  }
},

/**
 * méthode unique appellée sur le onunload
 */
_onUnLoad : function()
{
  // on supprime le DOMLoaded (Fx/O9), onbeforeunload et onunload automatiquement insérés
  EVT.remove($D, 'DOMContentLoaded', EVT.DOMLoaded);
  EVT.remove($W, 'beforeunload', EVT._onBeforeUnLoad);
  EVT.remove($W, 'unload', EVT._onUnLoad);

  var U = EVT.$U;
  for ( var i = 0, m = U.length; i < m; i++ )
  {
    try
    {
      U[i]();
    }
    catch(x)
    {
      /*
      alertError('Erreur lors du déchargement' + CR + x + U[i].toString());
      */
    }
  }
  // Supprime les events qui n'ont pas été correctement effacés par leurs éléments
  if ( EVT.L && EVT.L.length > 0 )
  {
    for ( var j = 0, n = EVT.L.length; j < n; j++ )
    {
      var L = EVT.L[j];
      if ( L )
      {
        EVT.remove(L[0], L[1], L[2]);
      }
    }
  }
},

/**
 * Méthode appelée sur le onbeforeunload
 */
_onBeforeUnLoad : function()
{
  if ( typeof $W.$$app !== 'undefined' )
  {
/*
    var c = document;
    var B = c.body;
    var d = c.createElement('div');
    var ds = d.style;
    var s = c.createElement('span');
    var ss = s.style;

    ds.zIndex = 1e6;
    ds.textAlign = 'center';
    ds.position = 'absolute';
    ds.backgroundColor = 'white';
    ds.top = ds.left = '0px';
    DOM.setOpacity(d, '0.50');
    ds.width = ( parseInt(B.offsetWidth, 10) + 50 ) + 'px';
    ds.height = ds.lineHeight = ( parseInt(B.offsetHeight, 10) + 50 ) + 'px';
    ds.backgroundColor = 'black';

    s.id = 'AIEVT_unloader';
    ss.verticalAlign = 'middle';
    ss.color = 'white';
    ss.fontFamily = 'arial';
    ss.fontWeight = '800';
    ss.fontSize = '18px';

    s.appendChild(c.createTextNode('DECHARGEMENT EN COURS'));
    d.appendChild(s);

    B.style.overflow = 'hidden';
    B.appendChild(d);
*/
    var B = $D.body;
    var H = DOM.pix(B.offsetHeight, 50);
    
    B.style.overflow = 'hidden';

    $E(
      'div',
      {},
      {
        zIndex:1e6,
        textAlign:'center',
        position:'absolute',
        backgroundColor:'white',
        top:0,
        left:0,
        opacity:0.5,
        width:DOM.pix(B.offsetWidth, 50),
        height:H,
        lineHeight:H,
        backgroundColor:'black'
      },
      [
        $E(
          'span',
          {id:'AIEVT_unloader'},
          {
            verticalAlign:'middle',
            color:'white',
            fontFamily:'arial',
            fontWeight:800,
            fontSize:'18px'
          },
          [$dct(EVT.msg_unload)]
        )
      ],
      // si non vide, ca fait appendChild. donc ici : B.appendChild(le div créé)
      'a', B
    );
  }
  return true;
},
/**
 * helper function pour ajouter un script sur le onload
 */
//loader : function(F, onDom)
loader : function(F, o)
{
//  if ( EVT.DOMLoaded.OK && onDom)
  if ( EVT.DOMLoaded.OK )
  {
    F();
  }
  else
  {
    EVT.$L.push(F);
  }
},

/**
 * helper function pour ajouter un script sur le onunload
 */
unloader : function(F) { EVT.$U.push(F); }

};

/*
---------------------------------------------------------------------------
  MODELE W3C DOM2
---------------------------------------------------------------------------
*/

if ( $D.addEventListener )
{
  /**
   * Associe l'élément, l'event et la fonction
   *
   * @param {Object}   E         L'élément html sur lequel associer l'event
   * @param {String}   T         Le type d'event à associer
   * @param {Function} F         La fonction que l'event appelle
   * @private
   */
  EVT.bind = function(E, T, F) { E.addEventListener(T, F, false); };
  
  /**
   * Détache l'élément, l'event et la fonction
   *
   * @param {Object}   E        L'élément html ou son id sur lequel détacher l'event
   * @param {String}   T        Le type d'event à détacher
   * @param {Function} F        La fonction que l'event appelle
   * @private
   */
  EVT.unbind = function(E, T, F) { E.removeEventListener(T, F, false); };


  /**
   * Empêche le comportement par défaut de l'event
   *
   * @param {Event} evt L'event
   * @public
   */
  EVT.preventDefault = function(e) { e.preventDefault(); };
  
  /**
   * Arrête la propagation de l'event
   *
   * @param {Event} evt L'event
   * @public
   */
  EVT.stopPropagation = function(e) { e.stopPropagation(); };
  
  /**
   * Obtient la direction et la distance parcourue par la molette
   *
   * @param {Event} evt L'event
   * @return (int) La direction (positive ou négative) parcourue
   * @public
   */
  EVT.getWheelDelta = function(e) { return -(e.detail || 0); };
  
  /**
   * Place un listener sur le scroll de la molette
   *
   * Utilise l'event DOMMouseScroll pour gecko et mousewheel pour IE
   *
   * @param {Object|String} E         L'élément html ou son id sur lequel assigner l'event
   * @param {Function}      F         La fonction que l'event appelle
   * @param {Object}        S         Scope d'exécution
   * @param {Object}        B         Un objet qui sera passé comme paramètre au gestionnaire
   * @param {boolean}       D         true si le modele DOM0 est imposé
   * @return {boolean}      true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
   * @public
   */
  EVT.addWheel = function(E, F, S, B, D) { return EVT.add(E, "DOMMouseScroll", F, S, B, D); };

  /**
   * Supprime le listener sur le scroll de la molette
   *
   * Utilise l'event DOMMouseScroll pour gecko et mousewheel pour IE
   *
   * @return {boolean}      true si l'action a réussi, false si la fonction n'a pas pu être attachée à l'élément
   * @public
   */
  EVT.removeWheel = function(E, F) { return EVT.remove(E, "DOMMouseScroll", F); };

  /**
   * Correspondance des boutons
   *
   * @type hash
   * @public
   */
  EVT.buttons = { left: 0, right: 2, middle: 1 };
  
  /**
   * Retourne la cible de l'event
   *
   * @param (Event) e L'event
   * @public
   */
  EVT.getTarget = function(e) { return ( e.target.nodeType == $D.TEXT_NODE ) ? e.target.parentNode : e.target; };
}
else if ( $D.attachEvent )
{ 
/*
---------------------------------------------------------------------------
  MODELE IE
---------------------------------------------------------------------------
*/
  EVT.bind = function(E, T, F) { E.attachEvent('on' + T, F); };
  EVT.unbind = function(E, T, F) { E.detachEvent('on' + T, F); };
  EVT.preventDefault = function(e) { e = EVT.fix(e); e.returnValue = false; };
  EVT.stopPropagation = function(e) { e = EVT.fix(e); e.cancelBubble = true; };
  EVT.getWheelDelta = function(e) { return e.wheelDelta ? e.wheelDelta / 40 : 0; };
  EVT.addWheel = function(E, F, S, B, D) { EVT.add(E, 'mousewheel', F, S, B, D); };
  EVT.removeWheel = function(E, F) { return EVT.remove(E, "mousewheel", F); };
  EVT.buttons = { left: 1, right: 2, middle: 4 };
//  EVT.getTarget = function(e) { return e.srcElement; };
  EVT.getTarget = function(e) { return e && e.srcElement ? e.srcElement : null; };
/*
  EVT.getScrollOriginalFromYahoo = function(S)
  {
    var d = document.documentElement;
    var b = document.body;
    if ( d && d['scroll' + S] )
    {
      return d['scroll' + S];
    }
    else if ( b )
    {
      return b['scroll' + S];
    }
    return 0;
  };
*/
  EVT.getScroll = function(S)
  {
    var d = IS.strict() ? $D.documentElement : $D.body;
    if ( typeof d['scroll' + S] != 'undefined' )
    {
      return d['scroll' + S];
    }
    return 0;
  };
}

/*
---------------------------------------------------------------------------
  EVENTS DE CHARGEMENT
---------------------------------------------------------------------------
*/
//EVT.add(window, 'load', EVT._onLoad);
// déporté vers event_DOMContentLoaded.js

/*
---------------------------------------------------------------------------
  EVENTS DE DECHARGEMENT
---------------------------------------------------------------------------
*/
EVT.add($W, 'unload', EVT._onUnLoad);
EVT.add($W, 'beforeunload', EVT._onBeforeUnLoad);

// backward compatibility, garder les formes courtes et supprimer les longues dès que possible
EVT.stopEvent = EVT.stop;
EVT.addEvent = EVT.add;
EVT.removeEvent = EVT.remove;
EVT.addEventMouseWheel = EVT.addWheel;
EVT.getPageX = EVT.getX;
EVT.getPageY = EVT.getY;
EVT.DOMLoaded = function()
{
  if ( EVT.DOMLoaded.OK ) { return true; }
  EVT.DOMLoaded.OK = true;
  if ( EVT.DOMLoaded.toid ) { $W.clearInterval(EVT.DOMLoaded.toid); }
//  var X = EVT.DOMLoaded;
//  if ( X.OK ) { return true; }
//  X.OK = true;
//  if ( X.toid ) $W.clearInterval(X.toid);
  EVT._onLoad();
  return true;
};

//Mozilla Opera9
if ( $D.addEventListener ) { EVT.add($D, 'DOMContentLoaded', EVT.DOMLoaded); }

//IE
/*@cc_on
$D.write('<script id="ieDOMload" defer src="javascript:void(0)"><\/script>');
var S = $('ieDOMload');
S.onreadystatechange = function(){if(this.readyState=='complete'){EVT.DOMLoaded();}};
@*/
/**
 MAJ plus compacte, censée marcher jusqu'à IE7b3
$D.getElementsByTagName('head')[0].appendChild($E('script',{defer:'defer',src:'//0',onreadystatechange:function(){if(this.readyState=='complete'){EVT.DOMLoaded();}}}));
*/


//Safari
//if ( /WebKit/i.test(navigator.userAgent) )
if ( /khtml|webkit/i.test(navigator.userAgent) )
{
  EVT.DOMLoaded.toid = $W.setInterval(function(){if(/loaded|complete/.test($D.readyState)){EVT.DOMLoaded();}},10);
}

//other browsers
$W.onload = EVT.DOMLoaded;
/**
 * création générique de la requête AJAX GET / POST
 * http://www.google.fr/search?hl=fr&q=setRequestHeader+xmlhttprequest&btnG=Rechercher&meta=
 * http://www.openweb.eu.org/articles/objet_xmlhttprequest/
 * http://kb.mozillazine.org/XMLHttpRequest
 */

var AJAX = {};

/**
 * liste des requetes effectuées.
 */
AJAX._requetes = [];


/**
 *
 * @param {string}   mode           Le mode d'appel (GET || POST)
 * @param {string}   url            L'url à appeler
 * @param {object}   datas          Les données à transférer (utilisé que en POST)
 * @param {function} handler_ok     Function à appelée lors d'une réponse valide
 * @param {function} handler_erreur Function à appelée lors d'une réponse invalide
 * @param {function} handler_run    Function à appelée lors de l'attente de la réponse
 * @param {object}   oScope         Portée des handlers (permet de repositionner "this" à travers les appels)
 * @param {object}   objAccompagne  Objet exclusivement transmis lors de l'appel des handlers (permet de simuler les anciens fonctionnement par closures)
 * @constructeur
 */
AJAX.init = function(mode, url, datas, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
{
/*
  var req = false;
  try {
    req = new XMLHttpRequest();
  } catch(ex) {
    try {
       req = new ActiveXObject("Microsoft.XMLHTTP");
    } catch(ex) {
      try {
        req = new ActiveXObject("Msxml2.XMLHTTP");
      } catch(ex) {
        req = false;
      }
    }
  }
*/
  var req = false;
  try
  {
    req = new XMLHttpRequest();
  }
  catch(ex)
  {
    var XMLHTTP_IDS = [ 'MSXML2.XMLHTTP.5.0', 'MSXML2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP', 'Microsoft.XMLHTTP' ];
    for ( var i=0, ilen=XMLHTTP_IDS.length; i<ilen && !req; i++ )
    {
      try
      {
        req = new ActiveXObject(XMLHTTP_IDS[i]);
      } catch(ex) {}
    }
  }

  if ( !req )
  {
    alertError("Impossible de créer l'objet XMLHttpRequest. Pas de gestion AJAX");
  }

  if ( !req ) { return false; }

  AJAX._requetes.push(req);
  var content = null;
  if ( 'GET' == mode )
  {
    req.open('GET', url, true);
  }
  else
  {
    content = '';
    for ( var i in datas )
    {
      content += (content.length ? '&' : '') + i + '=' + encodeURIComponent(datas[i]);
    }
    req.open('POST', url, true);
    req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
  }

  req.onreadystatechange = function()
  {
    if ( req )
    {
      if ( typeof handler_run == 'function' )
      {
        handler_run.call( oScope? oScope:this, req, objAccompagne);
      }
      /* 4 : état "complete" */
      if ( req.readyState == 4 )
      {
//         200 : code HTTP pour OK
//        code est vide quand testé en local sans serveur web
//        if (req.status == "") {
        if ( req.status == 200 )
        {
          if ( typeof handler_ok == 'function' )
          {
            handler_ok.call( oScope? oScope:this, req, objAccompagne);
          }
          else if ( handler_ok )
          {
            alertError('Status de retour : ' + req.status + '\nRequete OK mais handler invalide. Please FIX');
          }
        }
        else
        {
          if ( typeof handler_erreur == 'function' )
          {
            handler_erreur.call( oScope? oScope:this, req, objAccompagne);
          }
          else if ( handler_erreur )
          {
            alertError('Status de retour : ' + req.status + '\nEn échec, abandon transaction AJAX');
          }
        }
        req = AJAX.dispose(req);
      }
    }
  };
    // prototype.js #245
    /* Force "Connection: close" for Mozilla browsers to work around
     * a bug where XMLHttpReqeuest sends an incorrect Content-length
     * header. See Mozilla Bugzilla #246651.
     */
//      if (req.overrideMimeType)
//      req.setRequestHeader('Connection', 'close');

  req.send(content);
  return true;
};

/**
 * détruit la requete et les handlers
 */
AJAX.dispose = function(req)
{
  try
  {
    if ( req )
    {
      var index = AJAX._requetes.indexOf(req);
      if ( typeof req.abort == 'function' )
      {
        req.abort();
      }
      req.onreadystatechange = null;
//      alerter(typeof index + '/' + index);
      if ( index !== -1 && AJAX._requetes[index] )
      {
        delete AJAX._requetes[index];
      }
    }
  } catch(x) {}
  req = null;
  return null;
};

//AJAX.current_token = null;
/*
 * création de la requête AJAX en méthode GET
 */
function AJAX_get(url, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
{
  return AJAX.init('GET', url, null, handler_ok, handler_erreur, handler_run, oScope, objAccompagne);
}
/*
 * création de la requête AJAX en méthode POST
 */
function AJAX_post(url, datas, handler_ok, handler_erreur, handler_run, oScope, objAccompagne)
{
  return AJAX.init('POST', url, datas, handler_ok, handler_erreur, handler_run, oScope, objAccompagne);
}

/**
 * Parse le résultat JSON sans eval()
 * mais ça marche pas quand la réponse est mal formée
 */
/*
AJAX.parse = function(txt)
{
  var r = null;
  try
  {
    var fn = new Function("return " + txt);
    r = fn();
  }
  catch(e)
  {
    eval("r = " + txt);
  }
  return r;
};
*/

/**
 * Parse le résultat JSON
 */
AJAX.parse = function(txt)
{
  var r = null;
  try { eval("r = " + txt); } catch(e) {}
  return r;
};

// efface toutes les requetes onunload
EVT.unloader(
  function()
  {
    var A = AJAX._requetes;
    var x = 0;
    for ( var i=A.length; i--; )
    {
      var R = A[i];
      if ( R ) { x++; }
      AJAX.dispose(R);
      R = null;
    }
    delete AJAX._requetes;
  }
);
//AJAX.etats = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
//var etat = AJAX.etats;

// informe le loader principal que le chargement de ce fichier est fini
//AI.setFileState('core/AJAX.js', AI.STATE_LOADED);