/**
 * Fonction javascript/jquery de gestion des préférences dans les listes.
 */
function Table() {

    this.doUpdate = false;
    this.initialized=false;
    
    this.initialize = function() {
        orion.tableprefs.initialize();
        orion.tableenhancer.initialize();
        this.initialized=true;
    };

    this.beforeAjax = function() {
        orion.table.doUpdate = true;
        this.initialized=false;
        
    };

    this.afterAjax = function() {
        if (orion.table.doUpdate) {
            orion.table.initialize();
            orion.table.doUpdate = false;
        }
    };
}

/**
 * Fonction javascript/jquery de gestion des préférences dans les listes.
 */
function TablePreferences() {
    this.regional = new Array();

    /**
     * booléen indique si les préféences doivent être activé ou non
     **/
    this.usePref = null;

    this.initialize = function() {
        /* Activation des fonctionnalités de configuration des préférences de listes */
        orion.tableprefs.activeListPreferences();
    };

    /**
     * Ajout d'un handler pour l'événement click du bouton enregister l'état courant du composant listPreferences    
     */
    this.addSaveHandler = function() {
        orion.event.listenToUsingSelector('click', 'a[data-o-role="saveCurrentListPreferences"]', function() {
            var table = $(this).closest('table[role="grid"]');

            //Prise en charge de la sauvegarde de l'ordre de tris sur les colonnes                        
            orion.tableprefs.saveSortingPreference(table);

            // Prise en charge de la sauvegarde de l'ordre des colonnes'            
            orion.tableprefs.saveColumnOrderPreference(table);

            // Prise en charge de la sauvegarde du Filtre local                                                     
            orion.tableprefs.saveLocalFilterPreference(table);

            alert(orion.tableprefs.regional["orion.tableprefs.save.confirmation"]);
        });
    };

    /**
     * Ajout d'un handler pour l'événement click du bouton Réinitialiser l'état de la liste : revenir à l'état initial    
     */
    this.addRestoreHandler = function() {
        orion.event.listenToUsingSelector('click', 'a[data-o-role="restoreInitialListPreferences"]', function() {
            var table = $(this).closest('table[role="grid"]');

            //Prise en charge de la sauvegarde de l'ordre de tris sur les colonnes                        
            orion.tableprefs.restoreInitialSortingPreference(table);

            // Prise en charge de la sauvegarde de l'ordre des colonnes'            
            orion.tableprefs.restoreInitialColumnOrderPreference(table);

            // Prise en charge de la sauvegarde du Filtre local                                                     
            orion.tableprefs.restoreInitialLocalFilterPreference(table);

            alert(orion.tableprefs.regional["orion.tableprefs.restore.confirmation"]);
        });
    };

    /**
     * Ajout d'un handler pour l'événement click du bouton Recharger les paramètres personnalisés de l'état de la liste    
     */
    this.addReloadHandler = function() {
        orion.event.listenToUsingSelector('click', 'a[data-o-role="reloadListPreferences"]', function() {

            var table = $(this).closest('table[role="grid"]');

            //Prise en charge de la sauvegarde de l'ordre de tris sur les colonnes                        
            orion.tableprefs.reloadSortingPreference(table);

            // Prise en charge de la sauvegarde de l'ordre des colonnes'            
            orion.tableprefs.reloadColumnOrderPreference(table);

            // Prise en charge de la sauvegarde du Filtre local                                                     
            orion.tableprefs.reloadLocalFilterPreference(table);

            alert(orion.tableprefs.regional["orion.tableprefs.reload.confirmation"]);
        });
    };

    /**  
     * Détermination du mode de stockage des préférences pour chaque table ou des préférences
     */
    this.prepareStorage = function() {
        $('input[data-o-role="listPreferencesStorageMode"]').each(function() {
            var storageMode = $(this).attr('value');
            var table = $(this).closest('table[role="grid"]');
            var listPreferenceStorageModeKey = (table.attr('id') + '_listPrefStorageMode');

            orion.storeInSessionStorage(listPreferenceStorageModeKey, storageMode);
        });
    };

    /**
     * Activve la gestion des préférences sur les listes
     */
    this.activeListPreferences = function() {
        orion.tableprefs.usePref = $('input[data-o-role="listPreferencesStorageMode"]').length !== 0;

        if (!orion.tableprefs.usePref) {
            return;
        }

        this.prepareStorage();
        this.addSaveHandler();
        this.addRestoreHandler();
        this.addReloadHandler();
    };

    /**
     * Fonction de sauvegarde de l'ordre initial des colonnes du tableau de la listes.
     */
    this.saveInitialColumOrder = function(table) {
        if (window.JSON) {
            var initialColumnOrderObj = table.powertable('getOrder', true);
            var initialColumOrderStorageKey = (table.attr('id') + '_initialColumnOrderObj');

            orion.tableprefs.saveValueInStore(initialColumOrderStorageKey, JSON.stringify(initialColumnOrderObj), table);
        }
    };

    /**
     * Fonction de sauvegarde de l'ordre initial de tris des colonnes du tableau de la listes.
     */
    //this.saveInitialSortingOrder = function (tablesorter, table) {           
    this.saveInitialSortingOrder = function(table) {
        if (window.JSON) {
            var initialSortingObj = table.config.sortList;
            if (initialSortingObj !== undefined) {
                var initialSortingStorageKey = ($(table).attr('id') + '_initialSortingObj');
                orion.tableprefs.saveValueInStore(initialSortingStorageKey, JSON.stringify(initialSortingObj), $(table));
            }
        }
    };

    /**
     * Recharge les préférences sur l'ordre des colonnes
     */
    this.reloadColumnOrderPreference = function(table) {
        if (window.JSON) {
            //Clé pour stocker l'ordre préférés des colonnes du tableau de cette listes
            var preferredColumOrderStorageKey = (table.attr('id') + '_preferredColumnOrderObj');
            var item = orion.tableprefs.getValueFromStore(preferredColumOrderStorageKey, table);
            var preferredColumnOrderObj = JSON.parse(item);
            if (preferredColumnOrderObj !== undefined && preferredColumnOrderObj !== null) {
                table.powertable('rebuild', preferredColumnOrderObj);
            }
        }
    };

    /**
     * Recharge les préférences sur l'ordre des tris sur les colonnes
     */
    this.reloadSortingPreference = function(table) {
        if (window.JSON) {
            //Clé pour récupérer la configuration préféré du tris sur les colonnes et la direction des tris sur celles-ci
            var preferredSortingStorageKey = (table.attr('id') + '_preferredSortingObj');
            var item = orion.tableprefs.getValueFromStore(preferredSortingStorageKey, table);
            var sorting = JSON.parse(item);
            if (sorting !== undefined && sorting !== null) {
                table.trigger("sorton", [sorting]);
            }
        }
    };

    /**
     * Reinitialise l'ordre des colonnes
     */
    this.saveColumnOrderPreference = function(table) {
        if (window.JSON) {
            //Récupére l'ordre courant des colonnes
            var currentColumnOrderObj = table.powertable('getOrder', false);
            //Sauve l'ordre courant'
            //Clé pour stocker l'ordre préférés des colonnes du tableau de cette listes
            var preferredColumOrderStorageKey = (table.attr('id') + '_preferredColumnOrderObj');

            orion.tableprefs.saveValueInStore(preferredColumOrderStorageKey, JSON.stringify(currentColumnOrderObj), table);
        }
    };

    /**
     * Recharge les préférences sur l'ordre des tris sur les colonnes
     */
    this.saveSortingPreference = function(table) {
        if (window.JSON) {
            var currentSortingObj = table[0].config.sortList;
            if (currentSortingObj !== undefined) {
                //Clé pour récupérer la configuration préféré du tris sur les colonnes et la direction des tris sur celles-ci
                var preferredSortingStorageKey = (table.attr('id') + '_preferredSortingObj');

                orion.tableprefs.saveValueInStore(preferredSortingStorageKey, JSON.stringify(currentSortingObj), table);
            }
        }
    };

    /**
     * Reinitialise l'ordre des colonnes'
     */
    this.restoreInitialColumnOrderPreference = function(table) {
        if (window.JSON) {
            //Clé pour récupérer l'ordre initial des colonnes du tableau de cette listes
            var initialColumOrderStorageKey = (table.attr('id') + '_initialColumnOrderObj');

            var item = orion.tableprefs.getValueFromStore(initialColumOrderStorageKey, table);
            var columnOrder = JSON.parse(item);
            if (columnOrder !== undefined && columnOrder !== null) {
                table.powertable('rebuild', columnOrder);
            }
        }
    };

    /**
     * Reinitialise l'ordre de tris sur les colonnes
     */
    this.restoreInitialSortingPreference = function(table) {
        if (window.JSON) {
            //Clé pour récupérer la configuration initiale du tris sur les colonnes et la direction des tris sur celles-ci
            var initialSortingStorageKey = (table.attr('id') + '_initialSortingObj');

            var item = orion.tableprefs.getValueFromStore(initialSortingStorageKey, table);
            var sorting = JSON.parse(item);
            if (sorting !== undefined && sorting !== null) {
                table.trigger("sorton", [sorting]);
            }
        }
    };

    /**
     * Enregistre le filtre local courant dans les préférences
     */
    this.saveLocalFilterPreference = function(table) {
        var input = table.find('div[data-o-role="local-filter"]').find('input[type="text"]');
        var currentLocalFilter = input.attr('value');
        if (currentLocalFilter !== undefined && currentLocalFilter !== null) {
            //Clé pour récupérer la configuration préféré du tris sur les colonnes et la direction des tris sur celles-ci
            var localFilterStorageKey = (table.attr('id') + '_localFilter');

            orion.tableprefs.saveValueInStore(localFilterStorageKey, currentLocalFilter, table);
        }
    };

    /**
     * Recharge le filtre local préféré à partir des préférences
     */
    this.reloadLocalFilterPreference = function(table) {
        var localFilterStorageKey = (table.attr('id') + '_localFilter');
        var localFilter = orion.tableprefs.getValueFromStore(localFilterStorageKey, table);
        if (localFilter !== undefined && localFilter !== null) {
            var input = table.find('div[data-o-role="local-filter"]').find('input[type="text"]');
            if (input.length > 0) {
                input.val(localFilter);
                input.parents('.form-group').removeClass('has-empty-value');
                orion.tableenhancer.applyLocalFilter(input);
            }
            else {
                orion.logger.log("orion.table.reloadLocalFilterPreference Local filter input not found !");
            }
        }
    };

    /**
     * Enregistre le filtre local initial dans les préférences
     */
    this.saveInitialLocalFilter = function(table, localFilter) {
        if (localFilter !== undefined && localFilter !== null) {
            //Clé pour récupérer la configuration préféré du tris sur les colonnes et la direction des tris sur celles-ci
            var initialLocalFilterStorageKey = (table.attr('id') + '_initialLocalFilter');

            orion.tableprefs.saveValueInStore(initialLocalFilterStorageKey, localFilter, table);
        }
    };

    /**
     * Restore le filtre local initial à partir des préférences
     */
    this.restoreInitialLocalFilterPreference = function(table) {
        var initialLocalFilterStorageKey = (table.attr('id') + '_initialLocalFilter');

        var localFilter = orion.tableprefs.getValueFromStore(initialLocalFilterStorageKey, table);
        if (localFilter !== undefined && localFilter !== null) {
            var input = table.find('div[data-o-role="local-filter"]').find('input[type="text"]');
            if (input.length > 0) {
                input.val(localFilter);
                orion.tableenhancer.applyLocalFilter(input);
            }
            else {
                orion.logger.log("orion.table.restoreInitialLocalFilterPreference Local filter input not found !");
            }
        }
    };

    /**
     * Retourne la façon de stocker les préférences : dans le local ou dans le session storage du navigateur.
     * 
     * @return true pour utiliser le localStorage, false pour utiliser le sessionStorage.
     */
    this.useLocalStorage = function(table) {
        var listPreferenceStorageModeKey = (table.attr('id') + '_listPrefStorageMode');
        var storageMode = orion.getFromSessionStorage(listPreferenceStorageModeKey);

        return storageMode == 'local';
    };

    /**
     * Enregistre dans le store du navigateur (local ou session) une clef et sa valeur.
     * 
     * @param key la clef à sauvegarder.
     * @param value la valeur à associer à cette clef.
     * @param table la table pour laquelle doit être sauvée cette clef.
     */
    this.saveValueInStore = function(key, value, table) {
        if (orion.tableprefs.useLocalStorage(table)) {
            orion.storeInLocalStorage(key, value);
        } else {
            orion.storeInSessionStorage(key, value);
        }
    };

    /**
     * Récupère depuis le store du navigateur (local ou session) la valeur associée à une clef.
     * 
     * @param key la clef à sauvegarder.
     * @param table la table pour laquelle doit être sauvée cette clef.
     * 
     * @return la valeur associée à la clef. Peut être "undefined" ou "null" si aucune valeur n'a été définit,
     * ou la clef jamais enregistrée.
     */
    this.getValueFromStore = function(key, table) {
        if (orion.tableprefs.useLocalStorage(table)) {
            return orion.getFromLocalStorage(key);
        } else {
            return orion.getFromSessionStorage(key);
        }
    };
}

/**
 * Fonction javascript/jquery pour enrichir les fonctionnalités de base 
 * des tableaux de données Orion.
 */
function TableEnhancer() {
    /**
     * Tableau des messages internationalisés
     */
    this.regional = new Array();
    /**
     * Map de stockage interne
     */
    this.map = new Map();

    this.initialize = function() {
        /* Activation des fonctionnalités de manipulation avancées de la table */
        orion.tableenhancer.activeTableMultiColumnBehavior();
        orion.tableenhancer.activeTableSortableColumnBehavior();
        orion.tableenhancer.activeTableMoveableColumnBehavior();
        orion.tableenhancer.activeLocalFilterBehavior();
        orion.tableenhancer.activeTableCheckBehavior();
        orion.tableenhancer.activeTableRowsStylesBehavior();

        orion.event.fireEvent(orion.event.TABLE_READY);
    };

    /**
     * Fonction appliquant les styles css sur les les lignes des table Orion
     * en fonction du type de lignes (ligne principale ou détail)
     * et du discriminant ligne paire/impaire
     */
    this.activeTableRowsStylesBehavior = function() {
        orion.tableenhancer.initializeRowsStyles();

        /**
         * Binde un callback sur l'événement localFilterEnd :
         *  pour mettre à jour la coloration des lignes paires/impaires
         *  visibles à l'écran à la fin d'un filtre local
         */
        orion.event.listenToUsingSelector("localFilterEnd", 'table[role="grid"]', orion.tableenhancer.applyRowsStyles);
    };

    /**
     * Initialize les styles css pour la coloration des lignes paires/impaires sur les tableaux
     * la coloration paire/impaire s'applique uniqument sur les lignes de données
     * visible à l'écran
     **/
    this.initializeRowsStyles = function() {
        $('table[role="grid"] > tbody > tr[data-o-row="true"]').each(function(index) {
            $(this).removeClass('o-table-row-striped-odd o-table-row-striped-even');
            if ($(this).is(":visible")) {
                if ((index % 2) === 0) {
                    $(this).addClass('o-table-row-striped-even');
                }
                else {
                    $(this).addClass('o-table-row-striped-odd');
                }
            }
        });
    };

    /**
     * Applique les styles css pour la coloration des lignes paires/impaires sur les tableaux
     * la coloration paire/impaire s'applique uniqument sur les lignes de données
     * visible à l'écran. Cette fonction est 'bindé' sur l'événement localFilterEnd levé par
     * le filtre local permettant ainsi la mise à jours des styles sur les lignes filtrés
     **/
    this.applyRowsStyles = function(event) {
        var targetTable = event.target;
        $(targetTable).find('tbody > tr[data-o-row="true"]').removeClass('o-table-row-striped-odd o-table-row-striped-even');
        $(targetTable).find('tbody > tr[data-o-row="true"]:visible').each(function(index) {
            if ((index % 2) === 0) {
                $(this).addClass('o-table-row-striped-even');
            }
            else {
                $(this).addClass('o-table-row-striped-odd');
            }
        });
    };

    /**
     * Active la sélection globale de toutes les lignes affichées de la table
     */
    this.activeTableCheckBehavior = function() {
        orion.event.listenToUsingSelector("click", 'input[data-o-role="check-all-selector"]', orion.tableenhancer.checkAllCheckBoxSelector);
    };

    /**
     * Coche/decoche tous les checkbox de sélection des lignes de la table
     */
    this.checkAllCheckBoxSelector = function() {
        var checkedAttributeValue = $(this).attr("checked");
        var table = $(this).closest('table[role="grid"]');

        table.find('> tbody > tr[data-o-row="true"]:visible > td input[data-o-role="selector"]').attr(
                'checked', checkedAttributeValue == "checked");
    };

    /**
     * Active la fonctionnalité de déplacement des colonnes du tableau
     */
    this.activeTableMoveableColumnBehavior = function() {
        $('table[role="grid"]').each(function() {
            
            var tableMoveable = $(this).data('powertable'); // Mantis 668 : Vérifier si le plugin powertable a été déjà appliqué (différent de undefined)
            if (tableMoveable !== undefined) {
                return;
            }
            
            var tr = $(this).find("thead > tr");
            /** 
             * Construction de l'objet Javascript/JSON défnissant les colonnes
             * pour lesquelles la fonctionnalité de masquage/affichage 
             * des colonnes est désactivé.
             * Cette fonctionnalité n'est pas utilisée actuellement :
             * Aucune colonne ne peut être masquée
             **/
            var showHideDisabledStr = '[';
            var nbTh = tr.find('th').length;
            tr.find('th').each(function(index) {
                var th = $(this);
                var idValue = th.attr("id");
                idValue = idValue + index;
                th.attr("data-ptcolumn", idValue);

                /**
                 * Redéfinition du handle pour le déplacement des colonnes
                 * pour ne pas utiliser le draghandle par défaut de powertable
                 * de manière à ajouter un span indiquant le but du lien qui porte
                 * le drag handle
                 */
                if (th.attr("data-o-moveable") == "true") {
                    $('<a class="o-table-header-draghandle" data-o-role="draghandle"><span class="o-table-header-dragpurpose">' + orion.tableenhancer.regional["orion.tableenhancer.move.table.column"] + '</span></a>').appendTo(th);
                }
                
                if (index < nbTh - 1) {
                    showHideDisabledStr = showHideDisabledStr + '"' + idValue + '",';
                } else {
                    showHideDisabledStr = showHideDisabledStr + '"' + idValue + '"';
                }
            });
            showHideDisabledStr = showHideDisabledStr + ']';
            var showHideDisabledObj = JSON.parse(showHideDisabledStr);
            /** 
             * Construction de l'objet Javascript/JSON défnissant les colonnes
             * qui ne peuvent pas être déplacées : les colonnes fixes
             * en se basant sur l'attribut moveable des tags th   
             **/
            var moveDisabledStr = '[';
            var nbThMovable = tr.find('th[data-o-moveable="false"]').length;
            tr.find('th[data-o-moveable="false"]').each(function(index) {
                var th = $(this);
                var dataPtColumnValue = th.attr("data-ptcolumn");
                if (index < nbThMovable - 1) {
                    moveDisabledStr = moveDisabledStr + '"' + dataPtColumnValue + '",';
                } else {
                    moveDisabledStr = moveDisabledStr + '"' + dataPtColumnValue + '"';
                }
            });
            moveDisabledStr = moveDisabledStr + ']';
            var moveDisabledObj = JSON.parse(moveDisabledStr);

            var masterTrIndex = tr.length - 1;

            /**
             * Recherche le tr qui correspond aux enteêtes de colonnes
             * ajoute une metadata pour indiquer qu'il s'agit
             * du tr contenant les headers
             */
            var headerTr = tr[tr.length - 1];
            $(headerTr).attr('data-o-header', 'true');
            /**
             * Configuration de la table pour le déplacement des colonnes
             * voir l'API du plugin powertable.js
             */
            $(this).powertable({
                masterRow: masterTrIndex,
                fixedColumns: moveDisabledObj,
                moveDisabled: moveDisabledObj,
                //definition du selecteur jquery à utiliser pour retouver le lien poue le draghandle :
                // deplacement des colonnes
                moveHandle: 'a[data-o-role="draghandle"]',
                moveHandleText: '',
                showHideDisabled: showHideDisabledObj,
                //ajout des fonctions de callback avant et après le déplacement des coonnes
                //pour la gestion des lignes de détails.                
                beforeMoveColumn: orion.tableenhancer.beforeMoveColumn
                ,
                afterMoveColumn: orion.tableenhancer.afterMoveColumn
            });

            /**
             * Initialisation des préférences dans le cas ou le composant
             * listPreferences est présent dans la listes
             **/
            if (orion.tableprefs && orion.tableprefs.usePref) {
                //Sauve l'ordre initial des colonnes dans les préféfences'
                orion.tableprefs.saveInitialColumOrder($(this));
                //Recharge les préférences sur l'ordre des colonnes
                orion.tableprefs.reloadColumnOrderPreference($(this));
            }
        });
    };

    /**
     * Function de callback apppelée avant le déplacement des colonnes 
     * d'un tableau : prend en charge les lignes autres ques des lignes
     * de données (lignes contenant le filte local et les préférecnces, lignes de détails ...) : 
     * elles doivent être supprimé et sauvegardé pour permettre 
     * le déplacement
     */
    this.beforeMoveColumn = function(e, fromIndex, toIndex) {
        var table = e.target;
        //Gestion des lignes de l'entête du tableau
        //sauvegarde des lignes autres que lignes de données 
        //en stockage interne au tableenhancer
        //et suppression de ces lignes avant le déplacement des colonnes
        var th = $(table).find('thead');
        var thClone = th.clone();
        //var dataTr = thClone.find('tr[data-o-row="true"]');
        var headerTr = thClone.find('tr[data-o-header="true"]');
        headerTr.remove();
        //dataTr.remove(); 
        var thContent = thClone.html();
        if (thContent !== undefined && thContent !== null) {
            var noColumnHeaderTrStorageKey = $(table).attr('id') + '_currentNoColumnHeaderTr';
            orion.tableenhancer.map.put(noColumnHeaderTrStorageKey, JSON.stringify(thContent));
        }
        //var tr = $(table).find('thead > tr[data-o-row!="true"]');
        var tr = $(table).find('thead > tr[data-o-header!="true"]');
        tr.remove();
        //Gestion des lignes dans le corps du tableau : lignes de détails
        orion.tableenhancer.removeAndSaveBodyNoDataTr($(table));
    };

    /**
     * Gestion des lignes dcorps de tableau
     * sauvegarde des lignes autres que lignes de données 
     * en stockage interne au tableenhancer
     * et suppression de ces lignes du corps du tableau avant le déplacement des colonnes.
     */
    this.removeAndSaveBodyNoDataTr = function(table) {
        //Gestion des lignes dans le corps du tableau : lignes de détails
        orion.tableenhancer.saveBodyNoDataTr(table);
        var tbody = table.find('tbody');
        var tr = tbody.find('tr[data-o-row!="true"]');
        tr.remove();
    };

    /**
     * Sauvegarde interne des lignes autres ques les lignes de données
     * et suppression de ces lignes du tableau, cette fonction est appelée
     * avant le déplacementdes colonnes du tableau
     **/
    this.saveBodyNoDataTr = function(table) {
        var tbody = table.find('tbody');
        var tbodyClone = tbody.clone();
        var tbodyDataTr = tbodyClone.find('tr[data-o-row="true"]');
        tbodyDataTr.remove();
        var tbodyContent = tbodyClone.html();
        if (tbodyContent !== undefined && tbodyContent !== null) {
            var noDataBodyTrStorageKey = table.attr('id') + '_currentNoDataBodyTr';
            orion.tableenhancer.map.put(noDataBodyTrStorageKey, JSON.stringify(tbodyContent));
        }
    };

    /**
     * Function de callback apppelée après le déplacement des colonnes 
     * d'un tableau : prend en charge les lignes autres ques les lignes
     * de données ((lignes contenant le filte local et les préférecnces, lignes de détails ...)) :
     *  elles ont été supprimées avant le déplcament des colonnes 
     * et doivent être re insérées dans le tableau
     * 
     */
    this.afterMoveColumn = function(e, fromIndex, toIndex) {
        var table = e.target;
        //Restitution des lignes autres que lignes de données de l'entête du tableau
        //à partir de la sauvegarde interne
        var thead = $(table).find('thead');
        var noColumnHeaderTrStorageKey = $(table).attr('id') + '_currentNoColumnHeaderTr';
        var trContentStr = orion.tableenhancer.map.get(noColumnHeaderTrStorageKey);
        if (trContentStr !== undefined && trContentStr !== null) {
            var trContent = JSON.parse(trContentStr);
            if (trContent !== undefined && trContent !== null) {
                thead.prepend(trContent);
            }
            orion.tableenhancer.map.remove(noColumnHeaderTrStorageKey);
        }
        //Gestion des lignes dans le corps du tableau : lignes de détails
        //Restitution des lignes autres que lignes de données
        //à partir de la sauvegarde interne
        orion.tableenhancer.restoreBodyNoDataTr($(table));
    };

    /**
     * Function restaurant les lignes autres ques les lignes de donées
     * dans le tabelau aprés déplcacement des colones
     *  (elles ont été supprimées avant le déplcament des colonnes 
     * et doivent être re insérées dans le tableau)
     */
    this.restoreBodyNoDataTr = function(table) {
        //récupérationdes lignes autres que les lignes de donées dans le stockage interne
        var tbody = table.find('tbody');
        var noDataBodyTrStorageKey = table.attr('id') + '_currentNoDataBodyTr';
        var noDataTrContentStr = orion.tableenhancer.map.get(noDataBodyTrStorageKey);
        if (noDataTrContentStr !== undefined && noDataTrContentStr !== null) {
            var noDataTrContent = JSON.parse(noDataTrContentStr);
            if (noDataTrContent !== undefined && noDataTrContent !== null) {
                tbody.append(noDataTrContent);
            }
            orion.tableenhancer.map.remove(noDataBodyTrStorageKey);
        }
        //Repositionnement des lignes autres que les lignes de données dans le tableau        
        var tbodyDataTr = tbody.find('tr[data-o-row="true"]');
        tbodyDataTr.each(function() {
            var ri = $(this).attr("data-o-ri");
            var tBodyNoDataTr = tbody.find('tr[data-o-row!="true"][data-o-ri="' + ri + '"]');
            $(this).after(tBodyNoDataTr);
        });
    };

    /**
     * Active la fonctionnalité de tris des colonnes du tableau
     */
    this.activeTableSortableColumnBehavior = function() {
        $('table[role="grid"]').each(function() {
            
            var tableSortable = $(this).data('tablesorter'); // Mantis 668 : Vérifier si le plugin tablesorter a été déjà appliqué (différent de undefined)
            if (tableSortable != undefined) {
                return;
            }
            
            var tr = $(this).find("thead > tr");
            /**
             * Construction de l'objet javascript/JSON défnissant les colonnes qui 
             * ne peuvent pas être triées
             */
            var headersStr = '{';
            var nbSortableTh = tr.find('th[data-o-sortable="false"]').length;
            tr.find('th[data-o-sortable="false"]').each(function(index) {
                var th = tr.find("th");
                var idx = th.index($(this));
                if (index < nbSortableTh - 1) {
                    headersStr = headersStr + '"' + idx + '": { "sorter": false},';
                } else {
                    headersStr = headersStr + '"' + idx + '": { "sorter": false}';
                }
            });
            headersStr = headersStr + '}';
            var headersObj = JSON.parse(headersStr);

            /**
             * function de callback aprés l'initialisation des tris
             * sur la table permettant de stocker l'ordre de tris initial
             * dans les préférences si elles sont activées
             **/
            $(this).on("tablesorter-initialized", function(event) {
                if (orion.tableprefs && orion.tableprefs.usePref) {
                    var table = event.target;
                    orion.tableprefs.saveInitialSortingOrder(table);
                }
            });

            /**
             * ajout d'un handle sous forme de lien pour le tris des colonnes
             * but ne pas masquer les autres zone cliquables qui peuvent être mise
             * dans un th car par défaut le plugin table sorter ecoute les  clicks
             * pour initier des tris sur le th entiers
             * 
             */

            tr.find('th[data-o-sortable="true"]').each(function(index) {
                var th = $(this);
                $('<span class="o-table-header-sorthandle" data-o-role="sort-handle"><span class="o-table-header-sortpurpose">' + orion.tableenhancer.regional["orion.tableenhancer.sort.table.column"] + '</span></span>').prependTo(th);
            });
            
            /**
             * Configuration de la table pour le tris des colonnes
             * voir l'API du plugin tablesorter.js
             */
            
            $(this).tablesorter({
                headers: headersObj,
                sortReset: true,
                sortRestart: true,
                // ne pas annuler la selection car le cas contraire 
                // la sélection est predu pour le déplacement de colonnes
                cancelSelection: false,
                dateFormat: "ddmmyyyy",
                // surcharge de l'extraction de texte pour prendre en compte
                //le cas des input texte (cas des slavelistes)'                
                textExtraction: function(node, table, cellIndex) {
                    return orion.tableenhancer.extractTextFromNode(node);
                },
                /**
                 * sélecteur pour le handle permettant de déclencher un tris
                 */
                selectorSortHandle: '> thead th div span[data-o-role="sort-handle"]',
                // classe CSS pour l'afficahge de l'icone de tris ascendant'
                cssAsc: "o-table-header-sort-up",
                // classe CSS pour l'affichage de l'icone de tris descendant
                cssDesc: "o-table-header-sort-down",
                // classe CSS pour l'affichage de l'icone de tris non triés
                cssHeader: "o-table-header"
            });

            /**
             * Bind un callback sur l'événement sortStart :
             *  pour supprimer les lignes de détails du tableau avant un tri             
             */
            $(this).on("sortStart", orion.tableenhancer.sortStart);

            /**
             * Bind un callback sur l'événement sortEnd :
             *  pour dans le cas des lignes de détails repositionner les lignes de détails
             * dans le tableau à la fin d'un tris 
             */
            $(this).on("sortEnd", orion.tableenhancer.sortEnd);

            /**
             * Initialisation des préférences dans le cas ou le composant
             * listPreferences est présent dans la listes
             **/
            if (orion.tableprefs && orion.tableprefs.usePref) {
                //Recharge les préférences : dans le cas ou des préférecnces existent déjà
                orion.tableprefs.reloadSortingPreference($(this));
            }
        });
    };

    this.extractTextFromNode = function(node) {
        var text = $(node).text();
        var nodeInput = $(node).find('input[type=text]');
        if (nodeInput.length > 0) {
            var value;
            nodeInput.each(function() {
                var newValue = $(this).attr('value');
                value = (newValue !== undefined && newValue !== '') ? newValue : '';
            });
            text = (value !== undefined && value !== '') ? value : text;
        }

        return text;
    };

    /**
     * Fonction de calback appelée avant l'application d'un tris sur les colonnes
     * le rôle de cette fonction est de supprimer les lignes de détails car le tri
     * ne doit pas s'appliquer sur celles-ci (ces lignes sont reinsérées à la fin du tri par le
     * plugin)    
     **/
    this.sortStart = function(event) {
        var table = event.target;
        var tbody = $(table).find('tbody');
        var tr = tbody.find('tr[data-o-row!="true"]');
        tr.remove();
    };

    /**
     * Fonction de calback appelée à la fin d'un tris sur les colonnes
     * le rôle de cette fonction est  :
     * - repositionner les lignes autres que les lignes de données (lignes de détails)
     * par rapport au ligne de données dans le tableau car elles sont déplacées en fin de tableau
     * par le tris.
     */
    this.sortEnd = function(event) {
        var table = event.target;
        //Gestion des lignes dans le corps du tableau : lignes de détails
        //Restitution des lignes autres que lignes de données
        //à partir de la sauvegarde en session        
        var tbody = $(table).find('tbody');
        //Repositionnement des lignes autres que les lignes de données dans le tableau        
        var tbodyDataTr = tbody.find('tr[data-o-row="true"]');
        tbodyDataTr.each(function() {
            var ri = $(this).attr("data-o-ri");
            var tBodyNoDataTr = tbody.find('tr[data-o-row!="true"][data-o-ri="' + ri + '"]');
            $(this).after(tBodyNoDataTr);
        });

        // Mise à jour la coloration des lignes paires/impaires
        // visibles à l'écran à la fin d'un tris sur les colonnes
        orion.tableenhancer.applyRowsStyles(event);
    };

    /**
     * Active la fonctionnalité d'affichage d'un tableau multicolonnes : fonctionnalité utilisées
     * dans les guides.
     */
    this.activeTableMultiColumnBehavior = function() {
        $('table[role="grid"]').each(function() {
            // Récupération du nombre maximum d'élément par ligne pour
            // l'affichage multicolonne (nombre d'élément par colonne)        
            var tablebody = $(this).find('tbody');
            var groupRowsBy = tablebody.attr("data-o-grouprowsby");
            groupRowsBy = parseInt(groupRowsBy);
            if (groupRowsBy < 1 || groupRowsBy == NaN) {
                return;
            }

            // Récupération du nombre maximum d'élément dans la page
            //var size = jQuery(".table").attr("size");
            var size = tablebody.attr("data-o-size");
            size = parseInt(size);

            // Pas de traitement particulier s'il y a moins de ligne à afficher
            // que le nombre maxi de lignes par colonne,
            // OU si il y a moins d'éléments dans la table que d'éléments à
            // afficher par colonne
            if (size < groupRowsBy) {
                return;
            }

            // ///////////////////////////////////////////////
            // Refactoring de l'affichage en multicolonne
            // //////////////////////////////////////////////
            // On s'assure que le nombre de colonnes est bien un entier...
            var nbCols = size / groupRowsBy;
            nbCols = parseInt(nbCols);
            if (nbCols * groupRowsBy < size) {
                nbCols++;
            }

            // ///////////////////////////////////////////////
            // Refactoring des en-têtes :
            // Duplication du contenu du header pour avoir un en-tête par
            // colonne
            var header = $(this).find("thead").find("tr");
            var headerContent = header.html();

            // Le premier en-tête est déjà ajouté.
            var i = 1;
            // Tant qu'on a pas ajouté un en-tête par colonne
            // Le nombre d'en-tête dépend du nombre d'éléments à afficher...
            while (i < nbCols
                    && (i * groupRowsBy) < $(this).find('tbody > tr').length) {
                header.append(headerContent);
                i++;
            }

            // On supprime le contenu des en-têtes de colonnes
            // correspondant à la checkbox de sélection globale : Une seule
            // checkbox pour l'ensemble de la table.        
            header.find('th ~ th').each(function(index) {
                var th = $(this);
                th.find('input[data-o-role="check-all-selector"]').remove();
            });

            // ///////////////////////////////////////////////
            // Refactoring du corps :
            // Chaque nouvelle ligne contiendra le contenu de nbCols (anciennes
            // lignes) en colonnes.
            // On récupère le corps de la table
            var body = $(this).find('tbody');
            // Qu'on duplique afin d'en garder une copie
            var bodyTmp = body.clone();
            // Avant de le vider... afin de le re-remplir (tout en conservant
            // ses attributs initiaux)
            body.empty();

            // Pour chaque nouvelle ligne de la table
            i = 0;
            while (i < groupRowsBy) {
                // On récupère la ligne correspondante dans le copie de la table
                // Comme on récupère les lignes dans l'ordre, on peut conserver
                // leur classe
                var myTr = bodyTmp.find("tr:eq(" + i + ")").clone();
                // Et on l'ajoute au nouveau corps
                body.append(myTr);

                // Avant d'y ajouter de nouvelle lignes... en colonnes !
                // La première colonne est déjà renseignée, grâce à la
                // précédente ligne. L'index commence donc à 1.
                var j = 1;
                while (j < nbCols) {
                    // Et on va chercher la ligne correspondante dans l'ancienne
                    // table
                    var indexCell = i + (j * groupRowsBy);
                    if (indexCell < size) {
                        // Avant d'ajouter son contenu à cette nouvelle table.
                        myTr.append(bodyTmp.find("tr:eq(" + indexCell + ")")
                                .html());
                    }
                    j++;
                }

                // Puis on passe à la ligne suivante
                i++;
            }

            // Et on supprime la table temporaire
            bodyTmp.remove();
        });
    };

    /**
     * Active la fonctionnalité de filtre local des lignes du tableau des données 
     * de la liste
     */
    this.activeLocalFilterBehavior = function() {

        $("[data-o-role='local-filter'] input").live("keypress", function(event) {
            if (event.keyCode === 10 || event.keyCode === 13) {
                event.preventDefault();
            }
        });

        /**
         * Applique le filtre local initial
         * et enregistre le filtre local initial dans les 
         * préféfences sie elle existe
         */
        $("[data-o-role='local-filter']").each(function() {
            var widget = $(this);
            var defaultValue = widget.attr("data-o-default");
            var input = widget.find('input[type="text"]');
            input.attr('value', defaultValue);

            var filterValue = input.attr('value');
            var table = widget.parents('table[role="grid"]');
            /**
             * Enregistre le filtre local initial s'il existe
             * et s'il est différent du label Filtre Local
             */
            if (orion.tableprefs && orion.tableprefs.usePref) {
                orion.tableprefs.saveInitialLocalFilter(table, filterValue);
            }

            if (filterValue === undefined || filterValue == "") {
                orion.tableprefs.reloadLocalFilterPreference(table);
                return;
            }

            orion.tableenhancer.applyLocalFilter(input);
        });

        /**
         * Ajout d'un handler ecoutant l'événement  key up sur le champ
         * input du filtre local de manière à déclencher le filtrage lorsque l'utilisateur
         * saisie une valeur dans le champ
         */
        orion.event.listenToUsingSelector("keyup", "div[data-o-role='local-filter'] > input[type='text']", function(event) {
            if (event.keyCode == '13') {
                return false;
            }

            // on vérifie si le champ a du contenu ou pas pour afficher la croix de clear
            if ($(this).val() == '') {
                $(this).parents('.form-group').addClass('has-empty-value');
            } else {
                $(this).parents('.form-group').removeClass('has-empty-value');
            }

            orion.tableenhancer.applyLocalFilter($(this));
            return false;
        });

        /**
         * Évènement permettant de supprimer le contenu du LocalFilter d'un seul click sur la croix
         */
        orion.event.listenToUsingSelector("click", ".has-clear .form-control-clear", function(event) {
            var $input = $(this).parents('.form-group').find('input');
            $input.val('').trigger('change');
            $(this).parents('.form-group').addClass('has-empty-value');

            orion.tableenhancer.applyLocalFilter($input);

            // Trigger a "cleared" event on the input for extensibility purpose
            $input.trigger('cleared local filter');
        });

    };

    /**
     * Applique le filtre local sur les lignes de la table en utilisant la valeur saisie
     * dans le champ input du composant filtre local     
     */
    this.applyLocalFilter = function(input) {
        var filterValue = input.attr('value');

        // Noeud parent table le plus proche
        var table = input.parents('table[role="grid"]');

        var tbody = table.find('tbody');
        // Les lignes de données
        var lines = table.find('tbody tr[data-o-row="true"]');

        var label;

        if(filterValue === undefined){
            filterValue = "";
        }

        if (filterValue == "") {
            lines.each(
                    function() {
                        $(this).show();
                        //Gestion de l'affichage/masquage des lignes de détails
                        var ri = $(this).attr("data-o-ri");
                        var tBodyNoDataTr = tbody.find('tr[data-o-row!="true"][data-o-ri="' + ri + '"]');
                        tBodyNoDataTr.each(
                                function() {
                                    var displayState = $(this).attr("data-o-display-state");
                                    if (displayState !== undefined && displayState === 'visible') {
                                        $(this).show();
                                        $(this).removeAttr("data-o-display-state");
                                    }
                                }
                        );
                    }
            );

            table.trigger(orion.event.FILTER_END);
            label = input.prev();
            label.html("<span><b>" + orion.tableenhancer.regional["orion.tableenhancer.local.filter"] + " : </b></span>");

            return;
        }

        var count = 0;
        for (i = 0; i < lines.length; i++) {
            var tr = $(lines[i]);
            var datas = "";
            tr.children('td[role="gridcell"]').each(function() {
                //vérification si le td et filtrable
                var filterable = $(this).attr("data-o-filterable");
                // si la metadata n'est pas encore settée sur le td on récupère la valeur sur le th correspondant(effectué seulement la prémière fois)
                if (filterable === undefined) {
                    if ($(this).attr("headers")) {
                        var colHeader = table.find("th#" + $(this).attr("headers"));
                        var filterableHead = (colHeader.attr("data-o-filterable") === "true") ? "true" : "false";
                        $(this).attr("data-o-filterable", filterableHead);
                        filterable = filterableHead;
                    } else {
                        $(this).attr("data-o-filterable", false);
                        filterable = "false";
                    }
                }

                if (filterable === "true") {
                    var piece = orion.tableenhancer.extractTextFromNode($(this));
                    if (piece !== 'null') {
                        datas += piece.toLowerCase();
                    }
                }
            });

            if (datas.indexOf(filterValue.toLowerCase()) === -1) {
                tr.find('input[data-o-role="selector"]').removeAttr("checked");
                tr.hide();
                //Gestion de l'affichage/masquage des lignes de détails
                var ri = tr.attr("data-o-ri");
                var tBodyNoDataTr = tbody.find('tr[data-o-row!="true"][data-o-ri="' + ri + '"]');
                if (tBodyNoDataTr.is(":visible")) {
                    tBodyNoDataTr.attr("data-o-display-state", "visible");
                    tBodyNoDataTr.hide();
                }
            } else {
                tr.show();
                count++;
            }
        }

        table.trigger(orion.event.FILTER_END);

        label = input.prev();
        label.html("<span><b>" + orion.tableenhancer.regional["orion.tableenhancer.local.filter"] + " (" + count + ") : </b></span>");
    };

    /**
     * Map de stockage interne : sotckage d'objet sur le mode clé/valeur
     */
    function Map() {
        this.internal = [];
    }
    /**
     * Getter interne permettant de récupérer un objet clé/valeur
     * à partir de la clé key
     */
    Map.prototype._get = function(key) {
        for (var i = 0, keyvaluepair; keyvaluepair = this.internal[i]; i++) {
            if (keyvaluepair[0] === key) {
                return keyvaluepair;
            }
        }
    };

    /**
     * Stocke l'objet value dans la map sous la clé key
     */
    Map.prototype.put = function(key, value) {
        var keyvaluepair = this._get(key);
        if (keyvaluepair) {
            keyvaluepair[1] = value;
        } else {
            this.internal.push([key, value]);
        }
    };

    /**
     * Supprime l'objet value dans la map stocké sous la clé key
     */
    Map.prototype.remove = function(key) {
        for (var i = 0, keyvaluepair; keyvaluepair = this.internal[i]; i++) {
            if (keyvaluepair[0] === key) {
                this.internal.splice(i, 1);
                return true;
            }
        }

        return false;
    };

    /**
     * Retourne l'objet de la map stocké sous la clé key
     * ou null si aucun objet n'a été stocké aevc cette clé
     */
    Map.prototype.get = function(key) {
        var keyvaluepair = this._get(key);
        if (keyvaluepair) {
            return keyvaluepair[1];
        }
        return null;
    };
}


orion.event.TABLE_READY = "orion.table.ready";
orion.event.FILTER_END = "orion.table.filter.end";

orion.event.listenToTableReady = function(handler) {
    $(document).on(orion.event.TABLE_READY, handler);
};

orion.event.listenToLocalTableFilterEnd = function(handler) {
    $(document).on(orion.event.FILTER_END, handler);
};

orion.table = new Table();
orion.tableprefs = new TablePreferences();
orion.tableenhancer = new TableEnhancer();

$(document).ready(function() {
    orion.table.initialize();
    orion.event.listenToBeforeAjax(orion.table.beforeAjax);
    orion.event.listenToAfterAjax(orion.table.afterAjax);
});