/**
 * La fonction __init se charge de gérer et charger tous les effets et contenus
 * lorsque la page est chargée.
 * @param	element	chroot	La racine à partir de laquelle on initialise.
 */
function __init(chroot)
{
   // Chroot du système d'initialisation
   if (!$defined(chroot)) {
      chroot = $$('body').shift();
   }

   // Traitement des menus
   var navigation = new Zao_Navigation();
   navigation.init(chroot);

   // Traitement des systèmes d'onglets'
   var tabSystem = new Zao_TabSystem();
   tabSystem.init(chroot);

   // Traitement des formulaires
   var form = new Zao_Form();
   form.init(chroot);

   // Traitement des datagrids
   var datagrid = new Zao_Datagrid();
   datagrid.init(chroot);
}

// EVOL Si quelqu'un est capable de trouver une meilleure solution au problème des identifiants dupliqués, il est le bienvenu.
Element.implement({
   getElementWithPotentiallyDuplicatedId: function(id) {
      return this.getElement('*[id='+id+']');
   }
});

/**
 * Objet Zao_Uri étendant l'objet URI de mootools en y adaptant le format des URI.
 * Permet de spécifier que la queryString est précédée de '/' au lieu de '?'
 * et que les paramètres sont séparés par '/' au lieu de '&'
 * et que les associations avec les valeurs sont faites avec '/' au lieu de '='
 */
var Zao_Uri = new Class({

   Extends: URI,

   combine: function(bits){
      return bits.value || bits.scheme + '://' +
         (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
         (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
         (bits.directory || '/') + (bits.file || '') +
         (bits.query ? '/' + bits.query : '') +
         (bits.fragment ? '#' + bits.fragment : '');
   },

   setData: function(values, merge, part){
      if (typeof values == 'string'){
         values = this.getData();
         values[arguments[0]] = arguments[1];
      } else if (merge) {
         values = $merge(this.getData(), values);
      }
      return this.set(part || 'query', Hash.toZaoQueryString(values));
   }

});

/**
 * Méthode toZaoQueryString sur le Hash de mootools, utile pour Zao_Uri
 */
Hash.implement({
   toZaoQueryString: function(base){
      var queryString = [];
      Hash.each(this, function(value, key){
         if (base) key = base + '[' + key + ']';
         var result;
         switch ($type(value)){
            case 'object': result = Hash.toQueryString(value, key); break;
            case 'array':
               var qs = {};
               value.each(function(val, i){
                  qs[i] = val;
               });
               result = Hash.toQueryString(qs, key);
            break;
            default: result = key + '/' + encodeURIComponent(value);
         }
         if (value != undefined) queryString.push(result);
      });

      return queryString.join('/');
   }
});

/**
 * Objet Zao_Form contenant les méthodes ayant un rapport avec les formulaires.
 * (super intéressant comme commentaire)
 */
var Zao_Form = new Class(
{
   singleton: true,
   extensibleFormFieldPrefix: 't',

   /**
   * Initialisation des formulaires éventuellement présents sur la page.
   * @param	element	chroot	La racine à partir de laquelle on initialise.
   */
   init: function(chroot)
   {
      // Initialisation des boutons retour des formulaires
      chroot.getElements('button.form-goback').each(function(button) {
         button.removeEvents('click');
         button.addEvent('click', function(event) {
            var translator = new Zao_Translate();
            if (confirm(translator.translate('Voulez-vous vraiment quitter cette page ?'))) {
               if (!button.get('title')) {
                  window.history.go(-1);
               } else {
                  window.location = button.get('title');
               }
            }
            event.stop();
         }.bind(this));
      }.bind(this));

      // Initialisation des boutons reset des formulaires
      chroot.getElements('input.form-reset').each(function(button) {
         button.removeEvents('click');
         button.addEvent('click', function(event) {
            var form = button.getParent('form');
            this.emptyFormFields(form);
            event.stop();
         }.bind(this));
      }.bind(this));

      // Initialisation des boutons permettant d'afficher les TinyMCE
      chroot.getElements('.form-html-button').each(function(button) {
         button.removeEvents('click');
         button.addEvent('click', function(event) {
            var idTextarea = button.get('for');
            var textarea = $(idTextarea);

            if ($defined(textarea) && $defined(TINY_MCE)) {
               var profile = textarea.get('class').match(/form-html-[^\s]+/);

               if ($defined(TINY_MCE[profile])) {
                  var options = TINY_MCE[profile];
                  options.mode = 'exact';
                  options.elements = textarea.get('id');
                  tinyMCE.init(options);
                  button.dispose();
               }
            }
         }.bind(this));
      }.bind(this));

      // Initialisation des textarea éventuellement présents sur la page.
      chroot.getElements('textarea[class*=maxlength-]').each(function(textarea) {
         var classenames = textarea.get('class').split(' ');
         classenames.each(function(classname) {
            var matches = classname.match(/^maxlength-(\d+)$/);
            if (matches) {
               var max = matches.pop();
               var textareaEvent = function() {this.maxlength(textarea, max);}.bind(this);
               textarea.removeEvents('keyup');
               textarea.removeEvents('keypress');
               textarea.addEvent('keyup', textareaEvent);
               textarea.addEvent('keypress', textareaEvent);
            }
         }.bind(this));
      }.bind(this));

      // Initialisation des sous-formulaires extensibles
      chroot.getElements('.form-extensible').each(function(extensibleForm) {
         var templates = extensibleForm.getElements('.form-extensible-template');
         var first = templates.shift();
         first.hide();

         // On enlève les classes pour les datepicker du premier template
         var regexp = new RegExp("^dateformat-","g");
         first.getElements('input').each(function (element) {
            var classes = element.get('class');
            if (classes) {
               classes.split(' ').each(function(className) {
                  if (className.match(regexp)) {
                     element.removeClass(className);
                     element.addClass("date-no" + className.substr('4'));
                  }
               });
            }
         });

         extensibleForm.getElements('.form-extensible-template').each(function (template) {
            // Pour chaque template disponible dans le sous-formulaire, on ajoute le bouton pour supprimer la ligne
            if (!$defined(template.getElement('span.form-extensible-template-del'))) {
               var del = new Element('span', {
                  'class': 'form-extensible-template-del'
               });
               template.adopt(del);
            }
            // On attache l'event de suppression au template trouvé
            this.attachDeleteExtensibleFormTemplateEvent(template);
         }.bind(this));

         // On cherche le bouton permettant d'ajouter un template au sous-formulaire
         extensibleForm.getElements('.form-extensible-buttons button').each(function (button) {
            // Une fois trouvé, on lui attache l'event permettant d'ajouter un template
            button.removeEvents('click');
            button.addEvent('click', function() {
               this.addExtensibleFormTemplate(extensibleForm);
            }.bind(this));
         }.bind(this));
      }.bind(this));

      // Initialisation des groupes de champs rétractables sur les formulaires
      chroot.getElements('form.form').each(function(form) {
         form.getElements('dd.block').each(function(block) {
            if (!block.hasClass('block-static') && !block.hasClass('tab')) {
               var element = block.getChildren('dl').shift();
               var toggler = block.getElements('.block-title').shift();

               if ($defined(element) && $defined(toggler)) {
                  new Fx.Accordion([toggler], [element], {
                     show: true,
                     opacity: true,
                     alwaysHide: true,
                     initialDisplayFx: false,

                     onActive: function(toggler, element) {
                        var translator = new Zao_Translate();
                        toggler
                           .removeClass('block-title-inactive')
                           .addClass('block-title-active')
                           .setProperty('title', translator.translate('Replier'));
                        element
                           .removeClass('block-inactive')
                           .addClass('block-active');
                     },
                     onBackground: function(toggler, element) {
                        var translator = new Zao_Translate();
                        toggler
                           .removeClass('block-title-active')
                           .addClass('block-title-inactive')
                           .setProperty('title', translator.translate('Déplier'));
                        element
                           .removeClass('block-active')
                           .addClass('block-inactive')
                           .setStyle('height', element.getSize().y);
                     },
                     onComplete: function(element) {
                        if (element.getSize().y > 0) {
                           element.setStyle('height', 'auto');
                        }
                     }
                  });
                  element.setStyle('height', 'auto');
               }
            }
         }.bind(this));
      }.bind(this));

      // Initialisation des sous-formulaires d'images
      chroot.getElements('.form-images').each(function(uploadForm) {
         uploadForm.getElements('.form-images-image').each(function (image) {
            // Pour chaque image disponible dans le sous-formulaire, on ajoute le bouton pour déplacer l'image
            if (!$defined(image.getElement('span.form-images-image-mv'))) {
               var mv = new Element('span', {
                  'class': 'form-images-image-mv'
               });
               image.adopt(mv);
            }

            // Pour chaque image disponible dans le sous-formulaire, on ajoute le bouton pour supprimer la ligne
            if (!$defined(image.getElement('span.form-images-image-del'))) {
               var del = new Element('span', {
                  'class': 'form-images-image-del'
               });
               image.adopt(del);
            }
            // On attache l'event de suppression au template trouvé
            this.attachDeleteImagesFormImageEvent(image);
         }.bind(this));

         uploadForm.getElements('.form-images-upload').each(function (image) {
            // Pour chaque image disponible dans le sous-formulaire, on ajoute le bouton pour déplacer l'image
            if (!$defined(image.getElement('span.form-images-image-mv'))) {
               var mv = new Element('span', {
                  'class': 'form-images-image-mv'
               });
               image.adopt(mv);
            }
         }.bind(this));

         uploadForm.store('sortables', new Sortables(uploadForm.getElement('dl'), {
            handle: '.form-images-image-mv',
            revert: {duration: 500, transition: 'elastic:out'},
            onComplete: function() {
               uploadForm.getElements('.form-images-image-rank').each(function (rank, index) {
                  rank.set('value', index);
               });
            }
         }));
      }.bind(this));

      // Initialisation des champs dates
      try {
         datePickerController.create();
      } catch(e) {}
   },

   /**
   * Attache l'évènement de suppression d'une ligne de template d'un sous-formulaire
   * extensible au bouton correspondant.
   * @param	element	template	La ligne de template à traiter.
   * @return	boolean				true ou false selon si l'évènement a été attaché ou non.
   */
   attachDeleteExtensibleFormTemplateEvent: function(template)
   {
      // Si le bouton est présent dans le template, on lui attache l'évènement
      var button = template.getElement('span.form-extensible-template-del');
      if ($defined(button)) {
         button.removeEvents('click');
         button.addEvent('click', function() {
            this.deleteExtensibleFormTemplate(template);
         }.bind(this));
         return true;
      }
      return false;
   },

   /**
   * Action permettant de supprimer une ligne de template d'un sous-formulaire extensible.
   * @param	element	template	La ligne de template à supprimer.
   */
   deleteExtensibleFormTemplate: function(template)
   {
      var extensibleForm = template.getParent('.form-extensible');
      var templates = extensibleForm.getElements('.form-extensible-template');

      // S'il ne reste qu'une ligne, en recréé une
      if (templates.length <= 2) {
         this.addExtensibleFormTemplate(extensibleForm);
      }
      template.dispose();
      this.renumberExtensibleFormTemplates(extensibleForm);
   },

   /**
   * Action permettant d'ajouter une ligne de template à un sous-formulaire extensible.
   * @param	element	extensibleForm	Le sous-formulaire extensible dans lequel ajouter une ligne.
   * @return	boolean					true ou false selon si la ligne a été ajoutée ou non.
   */
   addExtensibleFormTemplate: function(extensibleForm)
   {
      var templates = extensibleForm.getElements('.form-extensible-template');
      var first = templates.shift();

      var last = first;
      if (templates.length > 0) {
         last = templates.pop();
      }

      // On clone le premier template et on l'ajoute après le dernier.
      var template = first.clone(true, true);

      // On remet les classes comme il faut pour le datepicker
      var regexp = new RegExp("^date-noformat-","g");
      template.getElements('input').each(function (element) {
         var classes = element.get('class');
         if (classes) {
            classes.split(' ').each(function(className) {
               if (className.match(regexp)) {
                  element.removeClass(className);
                  element.addClass('date' + className.substr(7));
               }
            });
         }
      });

      template.show();
      this.attachDeleteExtensibleFormTemplateEvent(template);

      template.injectAfter(last);

      this.renumberExtensibleFormTemplates(extensibleForm);
      __init(template);
   },

   /**
   * Action permettant de renuméroter les lignes de template d'un sous-formulaire
   * extensible pour les remettre dans l'ordre après un ajout ou une suppression.
   * @param	element	extensibleForm	Le sous-formulaire à renuméroter.
   * @return	int						Le nombre de templates qui ont été renumérotés.
   */
   renumberExtensibleFormTemplates: function(extensibleForm)
   {
      var increment = 0;
      extensibleForm.getElements('.form-extensible-template').each(function (template) {
         template.getElements('*[id]').each(function (element) {
            var id = element.get('id');
            id = id.replace(new RegExp("-"+this.extensibleFormFieldPrefix+"\\d+-"), '-'+this.extensibleFormFieldPrefix+increment+'-');
            element.set('id', id);
         }.bind(this));
         template.getElements('*[for]').each(function (element) {
            var id = element.get('for');
            id = id.replace(new RegExp("-"+this.extensibleFormFieldPrefix+"\\d+-"), '-'+this.extensibleFormFieldPrefix+increment+'-');
            element.set('for', id);
         }.bind(this));
         template.getElements('*').each(function(element) {
            var name = element.get('name');
            if ($chk(name)) {
               name = name.replace(new RegExp("\\["+this.extensibleFormFieldPrefix+"\\d+\\]"), '['+this.extensibleFormFieldPrefix+increment+']');
               element.set('name', name);
            }
         }.bind(this));
         increment++;
      }.bind(this));
      return increment;
   },

   /**
   * Attache l'évènement de suppression d'une image d'un sous-formulaire
   * image au bouton correspondant.
   * @param	element	image	La ligne de template à traiter.
   * @return	boolean			true ou false selon si l'évènement a été attaché ou non.
   */
   attachDeleteImagesFormImageEvent: function(image)
   {
      // Si le bouton est présent dans le template, on lui attache l'évènement
      var button = image.getElement('span.form-images-image-del');
      var rtn = false;
      if ($defined(button)) {
         button.removeEvents('click');
         button.addEvent('click', function(event) {
            var translator = new Zao_Translate();
            if (!window.confirm(translator.translate('Voulez-vous vraiment supprimer cette image ?'))) {
               event.stop();
               return false;
            }
            if (image.getParent('.form-images') && (sortable = image.getParent('.form-images').retrieve('sortables'))) {
               sortable.removeItems(image);
            }
            image.dispose();
            rtn = true;
            return true;
         }.bind(this));
      }
      return rtn;
   },

   /**
   * Vide les champs de formulaire situés sous l'élément passé en paramètre.
   * @param	element	element	L'élément à partir duquel vider les champs enfants.
   * @return	array			La liste des éléments qui ont été vidés.
   */
   emptyFormFields: function(element)
   {
      return element.getElements('input[type=text],input[type=password],input[type=file],textarea,select').set('value', '')
         .combine(element.getElements('input[type=checkbox],input[type=radio]').set('checked', ''));
   },

   /**
   * Limite dynamiquement la longueur des données contenues dans un champ de formulaire.
   * Tronque simplement la valeur à la longueur maximale autorisée.
   * @param	element	element	L'élément à traiter.
   * @param	int		max		Le nombre de caractères maximal.
   */
   maxlength: function(element, max)
   {
      var value = element.get('value');
      if (value.length >= max) {
         element.set('value',  value.substr(0, max));
      }
   }
});

/**
 * Objet Zao_Datagrid contenant les méthodes ayant un rapport avec les datagrids.
 * (super intéressant comme commentaire)
 */
var Zao_Datagrid = new Class(
{
   singleton: true,
   initialized: false,
   flickTimer: null,

   /**
   * Initialisation des formulaires éventuellement présents sur la page.
   * @param	element	chroot	La racine à partir de laquelle on initialise.
   */
   init: function(chroot)
   {
      // On ajoute ici l'évènement sur la case à cocher de la ligne de titre de chaque datagrid.
      chroot.getElements('input.datagrid-massaction-checkbox-reference').addEvent('click', this.checkAll);

      // On cache ici les boutons OK des actions multiples sur formulaire.
      chroot.getElements('input.datagrid-massaction-submit').setStyle('display', 'none');

      // On ajoute ici également l'évènement sur le <select> des actions multiples afin qu'il envoie le formulaire.
      chroot.getElements('select.datagrid-massaction-select').addEvent('change', this.multipleAction);

      // On ajoute un évènement click sur chaque ligne du datagrid
      chroot.getElements('.datagrid-data').addEvent('click', this.dataClick);

      // On ajoute un évènement change lorsque une checkbox "massaction" change de statut.
      chroot.getElements('.datagrid-massaction-checkbox').addEvent('change', this.dataCheckboxChange);

      // On ajoute l'évennement sur le double click sur une ligne
      chroot.getElements('.datagrid-data').addEvent('dblclick', this.dataDoubleClick);

      // Initialisation des filtres rétractables sur les datagrids
      chroot.getElements('.datagrid-filters-title').each(function(toggler) {
         var element = toggler.getNext('.datagrid-filters');
         var datagrid = element.get('id').replace('datagrid-filters-', '');
         var options = {
            opacity: true,
            alwaysHide: true,
            initialDisplayFx: false,

            onActive: function(toggler, element) {
               var translator = new Zao_Translate();
               toggler
                  .set('style', '')
                  .removeClass('datagrid-filters-title-inactive')
                  .addClass('datagrid-filters-title-active')
                  .setProperty('title', translator.translate('Replier'));
               element
                  .removeClass('datagrid-filters-inactive')
                  .addClass('datagrid-filters-active');

               this.stopFilterHighlight();
               if (this.initialized) {
                  new Request({
                     method: 'get',
                     url: BASE_URL+'/common/js/filters',
                     data: {'datagrid': datagrid, 'visibility' : 1}
                  }).send();
               }
            }.bind(this),
            onBackground: function(toggler, element) {
               var translator = new Zao_Translate();
               toggler
                  .set('style', '')
                  .removeClass('datagrid-filters-title-active')
                  .addClass('datagrid-filters-title-inactive')
                  .setProperty('title', translator.translate('Déplier'));
               element
                  .removeClass('datagrid-filters-active')
                  .addClass('datagrid-filters-inactive')
                  .setStyle('height', element.getSize().y);

               this.startFilterHighlight(toggler, element);
               if (this.initialized) {
                  new Request({
                     method: 'get',
                     url: BASE_URL+'/common/js/filters',
                     data: {'datagrid': datagrid, 'visibility': 0}
                  }).send();
               }
            }.bind(this),
            onComplete: function(element) {
               if (element.getSize().y > 0) {
                  element.setStyle('height', 'auto');
               }
            }.bind(this)
         };
         if (element.hasClass('datagrid-filters-active')) {
            options.show = 0;
         } else {
            options.show = -1;
         }
         new Fx.Accordion(toggler.getParent(), toggler, element, options);
		 
         toggler.set('morph', {duration: 'short', link: 'chain'});
         element.setStyle('height', 'auto');

		 this.startFilterHighlight(toggler, element);
      }.bind(this));

      // Déblocage des fonctionnalités nécessitant l'initialisation complète
      this.initialized = true;
   },

   startFilterHighlight: function(toggler, element, delay)
   {
      if (!$defined(delay)) {
         delay = 1000;
      }
      this.flickTimer = this.filterHighlight.delay(delay, this, [toggler, element]);
      return this;
   },

   stopFilterHighlight: function()
   {
      $clear(this.flickTimer);
      return this;
   },

   /**
   * Se charge de faire clignoter les filtres des datagrids lorsqu'ils sont
   * repliés mais qu'ils ont tout de même un effet sur l'affichage.
   * @param	element	filter	Le handler du filtre à traiter.
   * @param	element	element	Le filtre en lui même.
   * @return	boolean			true ou false en fonction du succès.
   */
   filterHighlight: function(toggler, element)
   {
      if (!toggler.hasClass('datagrid-filters-title-inactive')) {
         return false;
      }
      var used = false;
      element.getElements('input[type=text],input[type=password],input[type=file],textarea,select').get('value').each(function(value) {
         if (!used && !this.empty(value)) {
            used = true;
         }
      }.bind(this));
      element.getElements('input[type=checkbox],input[type=radio]').get('checked').each(function(checked) {
         used = used || checked;
      }.bind(this));

      if (!used) {
         return false;
      }
      toggler
         .morph('.datagrid-filters-title-highlight')
         .morph('.datagrid-filters-title-inactive');

      this.startFilterHighlight(toggler, element, 10000);
      return true;
   },

   /**
   * Cette fonction permet de cocher ou décocher toutes les cases des lignes du datagrid.
   * En réalité, les checkbox prennent le même état que celle qui est transmis en paramètre.
   * @param event event L'évènement qui a déclenché l'action
   */
   checkAll: function(event)
   {
      var element = $(event.target);
      var checked = element.get('checked');
      var form = element.getParent('form');
      form.getElements('input.datagrid-massaction-checkbox').each (function(checkbox) {
         var checkboxEvent = new Event(event.event);
         checkboxEvent.target = checkbox;
         checkbox.set('checked', checked);
         checkbox.fireEvent('change', checkboxEvent);
      });
   },

   /**
   * Cette fonction permet de réaliser une action multiple sur les lignes sélectionnées.
   * Concrétement, elle envoi le formulaire encapsulant le <select> concerné.
   * @param event event L'évènement qui a déclenché l'action
   */
   multipleAction: function(event)
   {
      var element = $(event.target);
      var form = element.getParent('form');

      if (!element.get('value')) {
         return false;
      }

      form.getElements('input.datagrid-massaction-checkbox').each(function(checkbox) {
         if (checkbox.get('checked')) {
            form.fireEvent('submit', event);
            form.submit();
         }
      });

      return false;
   },

   /**
   * Cette fonction permet de cocher/décocher la checkbox lorque l'utilisateur clique
   * sur la ligne.
   * @param event event L'évènement qui a déclenché l'action
   */
   dataClick: function(event)
   {
      var tags = ['input', 'select', 'textarea', 'button', 'label', 'option', 'optgroup', 'a'];
      if (!tags.contains($(event.target).get('tag'))) {
         var data = $(event.target).getParent('.datagrid-data');
         var checkbox = data.getElement('input.datagrid-massaction-checkbox');
         if (checkbox && checkbox != $(event.target)) {
            checkbox.set('checked', !checkbox.get('checked'));
            checkbox.fireEvent('change', event);
         }
      }
   },

   /**
   * Cette fonction permet d'executer une action javascript ou de suivre un lien présent sur la ligne
   * lorque l'utilisateur clique sur la ligne.
   * @param event event L'évènement qui a déclenché l'action
   */
   dataDoubleClick: function(event)
   {
      var tags = ['input', 'select', 'textarea', 'button', 'label', 'option', 'optgroup', 'a'];
      if (!tags.contains($(event.target).get('tag'))) {
         var data = $(event.target).getParent('.datagrid-data');
         data.getElements('.datagrid-doubleclick-action').each(function (action) {

            // On fire l'event (déclenchement des évennements JS)
            var evt = document.createEvent("MouseEvents"); // créer un évennement souris
            evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); // intiailser l'évennement déja crée par un click
            var clickEvent = new Event(evt);

            action.fireEvent('click', clickEvent);

            if (!clickEvent.event.cancelBubble && action.get('tag') == 'a' && action.get('href') != '') {
               // Si c'est un lien (et que le fireEvent ne l'a pas stoppé, on suit le lien
               window.location.href = action.get('href');
            }
         });
      }
   },

   /**
   * Cette fonction permet de réaliser d'ajouter/supprimer une classe à la ligne selectionnée
   * en fonction de la valeur récupérer de la checkbox.
   * @param event event L'évènement qui a déclenché l'action
   */
   dataCheckboxChange: function(event)
   {
      var data = $(event.target).getParent('.datagrid-data');
      if (data) {
         var checkbox = data.getElement('input.datagrid-massaction-checkbox');
         var dataClass = 'datagrid-data-selected';
         checkbox.get('checked') ? data.addClass(dataClass) : data.removeClass(dataClass);
      }
   },

   /**
   * Équivalent de la fonction empty() de PHP.
   * @param	mixed	mixed_var	Valeur à tester.
   * @return	boolean				true ou false selon si la valeur est vide.
   */
   empty: function(mixed_var)
   {
      var key;
      if ((mixed_var === "") ||
         (mixed_var === 0) ||
         (mixed_var === "0") ||
         (mixed_var === null) ||
         (mixed_var === false) ||
         (typeof mixed_var === 'undefined')) {
         return true;
      }
      if (typeof mixed_var == 'object') {
         for (key in mixed_var) {
            return false;
         }
         return true;
      }
      return false;
   }
});

/**
 * SuckerTree Horizontal Menu (Sept 14th, 06)
 * By Dynamic Drive: http://www.dynamicdrive.com/style/
 */
var Zao_Navigation = new Class(
{
   singleton: true,

   /**
   * Initialisation des menus éventuellement présents sur la page.
   * @param	elemen	chroot	La racine à partir de laquelle on initialise.
   */
   init: function(chroot)
   {
      var actions = $$('.actions').shift();
      if (!actions || !actions.getElement('li')) {
         return false;
      }
      var actionFixed  = new Element('div', {id: 'fixed'}).inject(document.body);
      var clone = actions.clone().inject(actionFixed);
      actionFixed.set('opacity','0').setStyle('display','block');

      /* scrollspy instance */
      var actions = new ScrollSpy({
         min: 170,
         onEnter: function(position,state,enters) {
            actionFixed.fade('0.85');
         },
         onLeave: function(position,state,leaves) {
            actionFixed.fade('out');
         },
         container: window
      });

      $('back-to-top').set('opacity','0');
      var backtotop = new ScrollSpy({
         min: 170,
         onEnter: function(position,state,enters) {
            $('back-to-top').fade('0.85');
         },
         onLeave: function(position,state,leaves) {
            $('back-to-top').fade('out');
         },
         container: window
      });
   }
});

/**
 * Objet Zao_Translate contenant les méthodes ayant un rapport avec la traduction.
 * (super intéressant comme commentaire)
 */
var Zao_Translate = new Class(
{
   singleton: true,

   /**
   * Confronte l'identifiant de message passé en paramètre à la liste des
   * identifiants de traduction disponibles pour le Javascript.
   * @param	string	messageId	L'identifiant de la chaîne de traduction à récupérer.
   * @return	string				La chaîne de traduction si elle a été trouvée ou l'identifiant si elle n'existe pas.
   */
   translate: function(messageId)
   {
      if ($defined(window.LOCALES) && LOCALES['js:'+messageId]) {
         return LOCALES['js:'+messageId];
      }
      return messageId;
   }
});

/**
 * Système d'onglet.
 * Rassemble les méthodes ayant un rapport avec les onglets.
 */
var Zao_TabSystem = new Class(
{
   singleton: true,

   /**
   * Initialisation des systèmes d'onglets éventuellement présents sur la page.
   * @param	elemen	chroot	La racine à partir de laquelle on initialise.
   */
   init: function(chroot)
   {
      // Traitement des onglets de formulaire
      // On traite chaque conteneur d'onglets l'un après l'autre
      chroot.getElements('.tab-system').each(function(tabSystem) {
         var active			= false;
         var parent			= tabSystem.getParent();
         var tabHeaders		= tabSystem.getElements('li.tab-header');
         var tabContainer	= tabSystem.getElement('.tab-container').empty();
         var fallbackTab		= null;

         // Dans chaque conteneur d'onglets, on traite chaque onglet
         tabHeaders.each(function(tabHeader) {
            var tabName		= tabHeader.get('id').split('-').pop();
            var tabActive	= tabHeader.hasClass('tab-header-active');
            var tab			= parent.getElementWithPotentiallyDuplicatedId('tab-'+tabName);
               active		= tabActive || active;

            // On récupère le conteneur correspondant à chacun des onglets trouvés
            if ($defined(tab)) {
               var tabTitle		= tab.getElement('.block-title');
               var tabHeaderLink	= tabHeader.getElement('a');
               tab.inject(tabContainer);

               // On ajoute l'event du clic souris sur l'onglet
               tabHeaderLink.removeEvents('click');
               tabHeaderLink.addEvent('click', function(event) {
                  var element = $(event.target);
                  this.toggle(tabSystem, element.getParent('li'));
                  event.stop();
               }.bind(this));

               // On peuple l'onglet avec le titre trouvé dans le conteneur
               if (tabHeaderLink && tabTitle) {
                  tabHeaderLink.set('html', tabTitle.get('html'));
                  tabTitle.getParent().dispose();

                  // Si des blocs d'erreur se trouvent dans le conteneur, on affiche leur nombre dans l'onglet
                  var errors = tab.getElements('.errors').length;
                  if (errors > 0) {
                     var translator = new Zao_Translate();
                     var span = new Element('span');
                     span.set('text', '('+errors+' '+((errors > 1) ? translator.translate('erreurs') : translator.translate('erreur'))+')');
                     span.inject(tabHeaderLink);
                     span.getParent('li').addClass('tab-header-error');
                     fallbackTab = tabName;
                  }
               }

               // On active le conteneur correspondant à l'onglet actif
               tab.removeClass('tab-active');
               tab.removeClass('tab-inactive');
               if (tabActive) {
                  tab.addClass('tab-active');
               } else {
                  tab.addClass('tab-inactive');
               }
            }
         }.bind(this));

         // Si aucun onglet actif n'a été trouvé, on active le premier par défaut
         if (!active) {
            var tabHeader	= null;
            var tabName		= null;

            // Si un des onglets contient des erreurs, on le qualifie d'office pour être ouvert par défaut
            if (!fallbackTab) {
               tabHeader	= tabHeaders.shift();
               if (tabHeader) {
                  tabName		= tabHeader.get('id').split('-').pop();
               }
            } else {
               tabName		= fallbackTab;
               tabHeader	= tabSystem.getElementWithPotentiallyDuplicatedId('tab-header-'+tabName);
            }

            // On active l'onglet par défaut
            var tab = tabSystem.getElementWithPotentiallyDuplicatedId('tab-'+tabName);
            if (tab) {
               tabHeader.addClass('tab-header-active');
               tab.removeClass('tab-inactive');
               tab.addClass('tab-active');
            }
         }
         // Enfin, on affiche le conteneur d'onglets complet, si il y a des onglets
         if (tabHeaders.length > 0) {
            tabSystem.setStyle('display', 'block');
         } else {
            tabSystem.setStyle('display', 'none');
         }
         // Mise en valeur des erreurs sur les onglets
         chroot.getElements('li.tab-header-error').set('morph', {duration: 'short', link: 'chain'});
      }.bind(this));

      this.highlightErrors.delay(1000, this, [chroot]);
      this.highlightErrors.periodical(2500, this, [chroot]);
   },

   /**
   * Affiche/cache un onglet et son conteneur correspondant.
   * @param	element			taxSystem	Le système d'onglet.
   * @param	string|object	tab			L'élément onglet ou même son conteneur voire l'identifiant de l'onglet.
   * @return	boolean						true si les choses se sont bien passées, false sinon.
   */
   toggle: function(tabSystem, tab)
   {
      if ($type(tab) == 'element') {
         tab = tab.get('id');
      }
      var tabId			= tab.split('-').pop();
      var tab_			= tabSystem.getElementWithPotentiallyDuplicatedId('tab-'+tabId);
      var tabContainer	= tab_.getParent('.tab-system');
      var tabHeaders		= tabContainer.getChildren('.tab-headers').shift().getElements('li.tab-header');

      tabHeaders.each(function(tabHeader) {
         var disableTabId	= tabHeader.get('id').split('-').pop();
         var disableTab		= tabSystem.getElementWithPotentiallyDuplicatedId('tab-'+disableTabId);

         tabHeader.removeClass('tab-header-active');
         if (disableTab) {
            disableTab.removeClass('tab-active');
            disableTab.addClass('tab-inactive');
         }
      });

      if (!tab_) {
         return false;
      } else {
         var tabHeader = tabSystem.getElementWithPotentiallyDuplicatedId('tab-header-'+tabId);
         if (tabHeader) {
            tabHeader.addClass('tab-header-active');
         }
         tab_.toggleClass('tab-active');
         tab_.toggleClass('tab-inactive');
      }
      return true;
   },

   /**
   * Se charge de faire clignoter les onglets des formulaires en cas d'erreur.
   */
   highlightErrors: function(chroot)
   {
      chroot.getElements('li.tab-header-error')
         .morph('.tab-header-error-highlight')
         .morph('.tab-header-error');
   }
});

/**
 * Objet Zao_Loading permettant de placer 'en chargement' un élément de la page.
 * La classe se contente d'apposer un calque sur l'objet en chargement.
 */
var Zao_Loading = new Class(
{
   /**
   * Durée des animations.
   * @var string
   */
   duration: 'short',

   /**
   * Opacité du calque apposé.
   * @var float
   */
   opacity: 0.75,

   /**
   * Affiche le calque de chargement sur l'objet passé en paramètre.
   * @param	string|object	element	L'élément à mettre en chargement (ou son identifiant).
   * @return	boolean					true ou false selon si le show() s'est bien passé ou non.
   */
   show: function(element)
   {
      if ($type(element) !== 'element') {
         element = $(element);
      }
      var loading = false;
      var classenames = element.get('class').split(' ');

      classenames.each(function(classname) {
         if (classname.test(/^loading-\d+$/)) {
            loading = true;
            return;
         }
      });
      if (loading) {
         return false;
      }

      var size = element.getCoordinates();
      var overlay = new Element('div');
      Zao_Loading.uniqid++;
      element.addClass('loading-'+Zao_Loading.uniqid);
      overlay.set('id', 'loading-'+Zao_Loading.uniqid);
      overlay.addClass('loading');
      overlay.setStyles({
         'opacity': 0,
         'position': 'absolute',
         'top': size.top,
         'left': size.left,
         'width': size.width,
         'height': size.height
      });

      overlay.set('tween', {duration: this.duration});
      overlay.tween('opacity', this.opacity);
      $$('body').shift().adopt(overlay);

      return true;
   },

   /**
   * Désactive le calque de chargement de l'objet passé en paramètre.
   * @param	string|object	element	L'élément à ne plus mettre en chargement (ou son identifiant).
   * @return	boolean					true ou false selon si le hide() s'est bien passé ou non.
   */
   hide: function(element)
   {
      if ($type(element) !== 'element') {
         element = $(element);
      }
      var result = false;
      var classenames = element.get('class').split(' ');
      classenames.each(function(classname) {
         if (classname.test(/^loading-\d+$/)) {
            result = this.hideOverlay($(classname));
         }
      }.bind(this));
      return result;
   },

   /**
   * Désactive le calque de chargement passé en paramètre.
   * @param	string|object	element	Le calque à cacher (ou son identifiant).
   * @return	boolean					true ou false selon si le hideOverlay() s'est bien passé ou non.
   */
   hideOverlay: function(overlay)
   {
      if ($type(overlay) !== 'element') {
         overlay = $(overlay);
      }
      if ($defined(overlay)) {
         var fading = new Fx.Tween(overlay, {duration: this.duration});
         fading.addEvent('complete', function() {overlay.dispose();});
         fading.start('opacity', 0);

         $$('.'+overlay.get('id')).each(function(element) {
            element.removeClass(overlay.get('id'));
         });
         return true;
      }
      return false;
   },

   /**
   * Désactive tous les calques de chargement.
   * @return	int	Le nombre de calques désactivés.
   */
   hideAll: function()
   {
      var result = 0;
      $$('div.loading').each(function(overlay) {
         if (this.hideOverlay(overlay)) {
            result++;
         }
      }.bind(this));
      return result;
   }
});
/**
 * Indentifiant unique permettant de retrouver les calques.
 * @var int
 */
Zao_Loading.uniqid = 0;

var Zao_AjaxAction = new Class (
{
   Implements: [Events],

   history: [],
   baseUrl: null,
   rootElement: null,
   interpretor: new Element('div'),
   loader: null,
   params: null,
   link: 'cancel',
   requestHtml: null,

   initialize: function(url, element, params, startOnInit)
   {
      this.requestHtml = new Request.HTML();

      url = new Zao_Uri(url);
      element = $(element);

      this.baseUrl = url;
      this.rootElement = element;
      if (params) {
         this.params = params;
      } else {
         this.params = {};
      }
      this.params.xmlHttpRequest = "xhr";

      this.loader = new Zao_Loading();

      if (startOnInit) {
         this.loader.show(this.rootElement);
         this.request(url);
      }
   },

   request: function(url, form, method)
   {
      // Url
      if (!url)
         url = this.baseUrl;

      // Méthode
      if (!method)
         method = 'get';

      url.set('data', this.params);

      // Requête
      this.requestHtml.removeEvents();
      this.requestHtml.setOptions({
         method: method,
         url: url.toString(),
         encoding: ENCODING,
         evalScripts: false,
         link: this.link,
         onSuccess: function(responseTree, responseElements, responseHTML, responseJavaScript) {
            this.history.push({
               'url': url,
               'form': form,
               'method': method
            });

            this.baseUrl = url;
            this.response(responseHTML);

            eval(responseJavaScript);

         }.bind(this),
         onFailure: function(response) {
            var translator = new Zao_Translate();
            this.loader.hide(this.rootElement);
            this.throwError(translator.translate("Une erreur s'est produite pendant le rafraichissement de ce composant. Veuillez retenter dans quelques instants et, si le problème persiste, merci de prendre contact avec nous au plus vite."));
         }.bind(this)
      }).send(form);
   },

   response: function(html)
   {
//		// Réécriture des id - A utiliser avec parcimonie
//		responseElements.each(function (tree)  {
//			tree.getElements('*').each(function (element) {
//				var idAttribute = element.get('id');
//				if (idAttribute && idAttribute.substring(0, 5) != 'ajax-') {
//					element.set('id', 'ajax-'+idAttribute);
//				}
//				var forAttribute = element.get('for');
//				if (forAttribute && forAttribute.substring(0, 5) != 'ajax-') {
//					element.set('for', 'ajax-'+forAttribute);
//				}
//			});
//		});

      this.interpretor.set('html', html);
      this.parseLink(this.interpretor);
      this.rootElement.empty().adopt(this.interpretor.getChildren());
      this.interpretor.empty();

      __init(this.rootElement);
      this.fireEvent('parse', this.rootElement);
   },

   parseLink: function(container)
   {
      this.loader.hide(this.rootElement);

      // Formulaires
      container.getElements('form:not([rel=download]):not([rel=javascript])').each(function(element) {
         var action		= element.get('action');
         var method		= element.get('method');
         var attributes	= ['class', 'style', 'id', 'title', 'lang', 'dir'];
         var dummy		= new Element('div');

         attributes.each(function(attribute) {
            var value = element.get(attribute);
            if ($chk(value)) {
               dummy.set(attribute, value);
            }
         });
         dummy.adopt(element.getChildren());
         dummy.inject(element, 'after');
         element.dispose();

         // Action de soumettre
         var submit = function(event) {
            if ((event.type != 'keypress') || (event.key == 'enter')) {
               this.loader.show(this.rootElement);
               var url = new Zao_Uri(action, {base: this.baseUrl});
               this.request(url, dummy, method);

               if ($defined(event)) {
                  event.stop();
               }
            }
         }.bind(this);

         // Action de vider
         var reset = function(event) {
            var form = new Zao_Form();
            form.emptyFormFields(this.rootElement);

            if ($defined(event)) {
               event.stop();
            }
         }.bind(this);

         // Boutons Submit
         dummy.getElements('button[type=submit], input[type=submit]').each(function (element) {
            var newElement = new Element(element.get('tag'));
            newElement.set('type', 'button');
            var attributes = new Hash(element.getProperties('disabled', 'name', 'size', 'value', 'accessKey', 'class', 'dir', 'id', 'lang', 'style', 'tabindex', 'title', 'xml:lang'));
            attributes.each(function(value, name) {
               newElement.set(name, value);
            });
            newElement.cloneEvents(element);
            newElement.replaces(element);
            newElement.addEvent('click', submit);
         });

         // Autres manières de soumettre
         dummy.getElements('button[type=submit], input[type=submit], input[type=image]').addEvent('click', submit);
         dummy.getElements('input[type=text], input[type=password]').addEvent('keypress', submit);

         // Boutons Reset
         dummy.getElements('button[type=reset], input[type=reset]').each(function (element) {
            var newElement = new Element(element.get('tag'));
            newElement.set('type', 'button');
            var attributes = new Hash(element.getProperties('disabled', 'name', 'size', 'value', 'accessKey', 'class', 'dir', 'id', 'lang', 'style', 'tabindex', 'title', 'xml:lang'));
            attributes.each(function(value, name) {
               newElement.set(name, value);
            });
            newElement.cloneEvents(element);
            newElement.replaces(element);
			newElement.removeClass('form-reset');
			newElement.addClass('form-reset-ajax');
            newElement.addEvent('click', reset);
         });
      }.bind(this));

      // Liens
      container.getElements('a:not([rel=download]):not([rel=javascript]),area:not([rel=download]):not([rel=javascript])').each(function(element) {
         element.addEvent('click', function(event) {
            this.loader.show(this.rootElement);
            var href = element.get('href');
            if (!href.test(/^#/)) {
               var url = new Zao_Uri (href, {base: this.baseUrl});
               this.request(url);
               if ($defined(event)) {
                  event.stop();
               }
            }
         }.bind(this));
      }.bind(this));

      // Goback
      container.getElements('.form-goback').each(function(element) {
         element.removeClass('form-goback');
         element.addClass('form-goback-ajax');
         element.addEvent('click', function(event) {
            this.loader.show(this.rootElement);
            this.history.pop();
            var request = this.history.pop();
            this.request(request.url, request.fom, request.method);
            if ($defined(event)) {
               event.stop();
            }
         }.bind(this));
      }.bind(this));
   },

   throwError: function(message)
   {
      // EVOL Un jour, il faudra coder une classe de gestion des erreurs en javascript. D'ici là, alert() fera office d'erreur.
      alert(message);
   }
});

var Zao_SelectList = new Class (
{
   name: null,
   mapping: null,
   formElements: null,
   listTable: null,
   ajaxAction: null,
   isSortable: true,
   selectedClass: 'selectlist-selected',
   limit: Infinity,
   replaceOnLimit: false,

   initialize: function(name, ajaxAction, listTable, mapping, formElements, sortable, limit, replaceOnLimit)
   {
      this.name = name;
      this.mapping = mapping;
      this.formElements = formElements;
      this.listTable = $(listTable).getElement('tbody');
      this.ajaxAction = ajaxAction;
      this.isSortable = sortable;
      this.limit = limit;
      this.replaceOnLimit = replaceOnLimit;

      this.refresh();
      this.parseResultTable();

      this.ajaxAction.addEvent('parse', this.parse.bind(this));
      
      var form = this.listTable.getParent('form');
      if (form) {
    	  form.addEvent('submit', this.submit.bind(this));
      }
   },

   submit: function()
   {
	   var cpt = 0;
	   var oldTr = null;
	   this.listTable.getElements('input[name*="[]"]').each(function (input) {
		   var tr = input.getParent('tr');
		   if (tr != oldTr) {
			   cpt++;
			   oldTr = tr;
		   }
		   var name = input.get('name');
		   name = name.replace("[]", "[" + cpt + "]");
		   input.set('name', name);
	   });
   }, 
   
   refresh: function()
   {
      if (this.isSortable) {
         this.makeSortable();
      }
      this.updateZebra();
   },

   makeSortable: function()
   {
      sortable = new Sortables(this.listTable, {handle:'.sorter-handler', opacity:0.6});
      sortable.addEvent('sort', this.updateZebra.bind(this));
   },

   updateZebra: function()
   {
      var count = 1;
      this.listTable.getChildren('tr').each(function (element) {
         if (count % 2 == 0) {
            element.removeClass('datagrid-odd')
            .addClass('datagrid-even');
         } else {
            element.removeClass('datagrid-even')
            .addClass('datagrid-odd');
         }
         count++;
      });
   },

   parse: function(rootElement)
   {
      rootElement.getElements('a.selectList-select').each(function(element) {
         element.removeEvents('click');
         element.addEvent('click', function(event) {
            var tr = element.getParent('tr');
            this.selectRow(tr);

            if ($defined(event)) {
               event.stop();
            }
         }.bind(this));
      }.bind(this));

      rootElement.getElements('tr[id]').each(function(row) {
         var matches = row.get('id').match(/row-(.*)$/i);
         var id = matches[1];
         var rowId = this.name + '-row-' + id;

         if ($(rowId)) {
            this.parseSelectedRow(id);
         }
      }.bind(this));

      rootElement.getElements('table.datagrid > thead > tr > th:last-child').each(function(element) {

         var span = new Element('span');
         span.addClass('selectList-selectAll-span');

         var translator = new Zao_Translate();
         var a = new Element('a');
         a.set('href','#');
         a.set('title', translator.translate('Sélectionner tous les éléments affichés'));
         a.addClass('selectList-selectAll');
         a.addEvent('click', function (event) {
            var table = $(event.target).getParent('table');
            var rows = table.getElements('tbody > tr:not([class~="selectlist-selected"])');
            rows.each(function (row) {
               this.selectRow(row);
            }.bind(this));
            event.stop();
         }.bind(this));

         var img = new Element('img');
         img.set('alt', translator.translate('Sélectionner'));
         img.set('src', BASE_URL + '/images/crudle/select.png');

         a.adopt(img);

         span.adopt(a);

         element.adopt(span);
      }.bind(this));
   },

   parseResultTable: function()
   {
      this.listTable.getElements('a.selectList-link-removeRow').each(function(element) {
         element.addEvent('click', function (event) {
			if (event) {
			   event.stop();
			}
            this.unselectRow(element.getParent('tr'));
         }.bind(this));
      }.bind(this));
   },

   parseSelectedRow: function(id, unselect)
   {
      var datagridId = this.ajaxAction.rootElement.getElement('div.datagrid-container').get('id');
      var row = $(datagridId + '-row-' + id);

      if (row) {
         if (!unselect) {
            row.addClass(this.selectedClass);
         } else {
            row.removeClass(this.selectedClass);
         }
      }
   },

   selectRow: function(row)
   {

      var matches = row.get('id').match(/row-(.*)$/i);
      var id = matches[1];
      var rowId = this.name + '-row-' + id;

      if ($(rowId)) {
         return this.unselectRow($(rowId));
      }

      var tr = this.listTable.getElements('tr');
      if (tr.length >= this.limit) {
    	  if (this.replaceOnLimit) {
    		  this.unselectRow(tr.shift());
    	  } else {
    		  return alert("Vous avez atteint la limite d'éléments sélectionnables. Veuillez supprimer des éléments avant d'en ajouter.");
    	  }
      }

      var tr = new Element('tr');
      tr.set('id', rowId);

      this.listTable.adopt(tr);
         this.mapping.each(function (value, key) {
            var element = row.getElement(value);
            var tag = null;
            var elementValue = null;

            // Element a récupérer
            if (element) {
               tag = element.get('tag');
               if (tag == 'input') {
                  elementValue = element.get('value');
               } else {
                  elementValue = element.get('text');
               }
            }

            var valueOnly = false;

            // La ligne
            var td;
            if (tag == 'td') {
               td = element.clone();
            } else {
               td = new Element('td');
            }

            tr.adopt(td);

            var container = td;

            // l'élément de formulaire
            var formElement = this.formElements[key];
            if (formElement) {
               td.empty().set('html', formElement);
               container = td.getElements('input');
               valueOnly = true;
            }

            // Le contenu
            if ($chk(elementValue)) {
               if (tag == 'td') {
                  if (valueOnly) {
                     container.set('value', elementValue);
                  }
               } else {
                  if (!valueOnly) {
                     var span = new Element('span');
                     span.setText(elementValue);
                     container.adopt('span');
                  } else {
                     container.set('value', elementValue);
                  }
               }
            }
         }.bind(this));

         var td = new Element('td');
         var input = new Element('input');
            input.set('type', 'hidden');
            input.set('name', this.name + '[id][]');
            input.set('value', id);
         td.adopt(input);

         var a = new Element('a');
            a.set('href', '#');
            a.addClass('selectList-link-removeRow');
            a.addEvent('click', function (event) {
			   if (event) {
			      event.stop();
			   }
               this.unselectRow(tr);
            }.bind(this));

               var translator = new Zao_Translate();
               var img = new Element('img');
                  img.set('src', BASE_URL + '/images/crudle/unselect.png');
                  img.set('alt', translator.translate('Retirer cet élément de la sélection'));
               a.adopt(img);

         td.adopt(a);

         if (this.isSortable) {
            a = new Element('a');
            a.set('href', '#');
            a.addClass('sorter-handler');
            a.addEvent('click', function (event) {
               event.stop();
            }.bind(this));
            img = new Element('img');
            img.set('src', BASE_URL + '/images/crudle/move.png');
            img.set('alt', translator.translate('Déplacer cet élément'));
            a.adopt(img);
            td.adopt(a);
         }
      tr.adopt(td);

      this.refresh();

      __init(tr);

      return this.parseSelectedRow(id);
   },

   unselectRow: function (row)
   {
      var matches = row.get('id').match(/row-(.*)$/i);
      var id = matches[1];

      row.dispose();
      this.refresh();

      return this.parseSelectedRow(id, true);
   }
});

var Zao_Sorter = new Class (
{
   tbody: null,

   initialize: function(datagrid)
   {
      this.tbody = datagrid.getElement('table > tbody');

      var sortable = new Sortables(this.tbody, {handle:'.sorter-handler', opacity:0.6});
      sortable.addEvent('sort', this.updateZebra.bind(this));

      this.tbody.getElements('.sorter-top').addEvent('click', function (event) {this.top($(event.target).getParent('tr'));return false;}.bind(this));
      this.tbody.getElements('.sorter-up').addEvent('click', function (event) {this.up($(event.target).getParent('tr'));return false;}.bind(this));
      this.tbody.getElements('.sorter-down').addEvent('click', function (event) {this.down($(event.target).getParent('tr'));return false;}.bind(this));
      this.tbody.getElements('.sorter-bottom').addEvent('click', function (event) {this.bottom($(event.target).getParent('tr'));return false;}.bind(this));
   },

   updateZebra: function()
   {
      var count = 1;
      this.tbody.getChildren('tr').each(function (element) {
         if (count % 2 == 0) {
            element.removeClass('datagrid-odd')
            .addClass('datagrid-even');
         } else {
            element.removeClass('datagrid-even')
            .addClass('datagrid-odd');
         }
         count++;
      });
   },

   top: function(element)
   {
      element.inject(this.tbody, 'top');
      this.updateZebra();
   },

   bottom: function(element)
   {
      element.inject(this.tbody, 'bottom');
      this.updateZebra();
   },

   up: function(element)
   {
      var previous = element.getPrevious();

      if (previous) {
         element.inject(previous, 'before');
         this.updateZebra();
      }
   },

   down: function(element)
   {
      var next = element.getNext();

      if (next) {
         element.inject(next, 'after');
         this.updateZebra();
      }
   }
});

/*
---
description:     ScrollSpy

authors:
  - David Walsh (http://davidwalsh.name)

license:
  - MIT-style license

requires:
  core/1.2.1:   '*'

provides:
  - ScrollSpy
...
*/
var ScrollSpy = new Class({

   /* implements */
   Implements: [Options,Events],

   /* options */
   options: {
      min: 0,
      mode: 'vertical',
      max: 0,
      container: window,
      onEnter: $empty,
      onLeave: $empty,
      onTick: $empty
   },

   /* initialization */
   initialize: function(options) {
      /* set options */
      this.setOptions(options);
      this.container = document.id(this.options.container);
      this.enters = this.leaves = 0;
      this.max = this.options.max;

      /* fix max */
      if(this.max == 0) {
         var ss = this.container.getScrollSize();
         this.max = this.options.mode == 'vertical' ? ss.y : ss.x;
      }
      /* make it happen */
      this.addListener();
   },

   /* a method that does whatever you want */
   addListener: function() {
      /* state trackers */
      this.inside = false;
      this.container.addEvent('scroll',function() {
         /* if it has reached the level */
         var position = this.container.getScroll();
         var xy = this.options.mode == 'vertical' ? position.y : position.x;
         /* if we reach the minimum and are still below the max... */
         if(xy >= this.options.min && xy <= this.max) {
               /* trigger Enter event if necessary */
               if(!this.inside) {
                  /* record as inside */
                  this.inside = true;
                  this.enters++;
                  /* fire enter event */
                  this.fireEvent('enter',[position,this.enters]);
               }
               /* trigger the "tick", always */
               this.fireEvent('tick',[position,this.inside,this.enters,this.leaves]);
         }
         else {
            /* trigger leave */
            if(this.inside)  {
               this.inside = false;
               this.leaves++;
               this.fireEvent('leave',[position,this.leaves]);
            }
         }
      }.bind(this));
   }
});

// Lance le traitement du javascript lorsque la page est prête
window.addEvent('domready', __init);

