'use strict';
define(function () {
  var gcelement = function (
    gclayers,
    ngTableParams,
    QueryFactory,
    EditFactory,
    FeatureTypeFactory,
    $filter,
    $rootScope,
    $timeout,
    gaDomUtils,
    gaJsUtils,
    ngDialog,
    gaBrowserSniffer,
    BacAppFactory,
    AncAppFactory,
    AlertHpoFactory,
    $sce,
    HpoCarteAppFactory,
    gaUrlUtils,
    $interval,
    CalendarFactory,
    bizeditProvider,
    mapJsUtils,
    CommonFactory,
    $q,
    RolesFactory,
    attributeRestrictionsUtils,
    RegionalFactory,
    ImportExportFactory,
    PortalsFactory, datatableIsHelper
  ) {
    return {
      templateUrl: 'js/XG/widgets/utilities/data/views/gcdatatable.html',
      restrict: 'A',
      scope: {
        map: '=?map',
        res: '=?res',
        fti: '=?fti',
        ftiupdateaction: '=?ftiupdateaction',
        ftid: '=ftid',
        actions: '=actions',
        actionsyn: '=actionsyn', // boutons actions synchronisation
        actionsynadd: '=actionsynadd',
        addfilter: '=addfilter',
        buttonfiltersynchro: '=buttonfiltersynchro',
        touslocaliser: '=touslocaliser',
        objsvisibles: '=objsvisibles',
        buttonactions: '=?buttonactions',
        where: '=?where',
        // -- KIS-3656: Quand appel depuis featuretreewidget (résultat de requête)
        // --   il faut connaître la requête pour pouvoir exporter les objets dans la datatable
        // --   on ajoutera à cette requête les filtres positionnés dans la datatable
        initialwhereclause: '=initialwhereclause',
        relationshow: '=?relationshow',
        coordsshow: '=?coordsshow',
        condfilter: '=?condfilter',
        fullFilter: '=?fullfilter',
        sensitive: '=?sensitive',
        attributes: '=?attributes',
        attributesToRound: '=?',
        geojsondata: '=?geojsondata',
        exportcsvgeojson: '=?exportcsvgeojson',
        filtre: '=?filtre',
        sort: '=?sort',
        geoj: '=?geoj',
        evaluate: '&',
        verifyupdated: '=?',
        verifyupdatedProvided: '@',
        verifydeleted: '=?',
        verifyissynchro: '=?',
        verifyadded: '=?',
        addwhere: '=?',
        addwhereupdate: '=?',
        addwheredelete: '=?',
        resetwhere: '=?',
        addobjects: '=?',
        localisertous: '=?',
        allowAdd: '=?',
        height: '@?height', // table height
        tableFillPage: '=?', // when set to true, force the table to fill the
        // reset of the remaining space
        singleSelection: '=?', // allow to select only one element,
        datawebservice: '&?', // specific webservice to retrieve the data when
        // extra information needs to be retrieved,
        noDefaultAction: '=?', // if set to true, the default actions
        // (edit/locate/delete) wont be shown,
        crud: '=?', // replace default actions with crud fuctions
        exportCsv: '@?', // add an export csv button of the currently
        exportCsvInThread: '=?', // exécute l'export dans un thread séparé
        // displayed data
        importCsv: '=?', // allow to import data into the table from a csv
        renduColonnes: '=?', // used to render column values (see renderValue
        // function)
        renduFilters: '=?', // used to render filter values (see renderFilter
        // function)
        extraFilters: '=?', // allows for extraFilters on columns
        dateFormat: '=?', // the template used to display dates (default
        // yyyy-MM-dd),
        resetFilterOnToggle: '=?', // when filter are hidden, they are reset
        displayTotalNumber: '=?', // display total elements number,
        displayTotalSelectedNumber: '=?', // display total selected elements
        // number,
        hideActLabel: '=?', // hide action label if true
        renduTitleColonne: '=?', // used to render column title (see
        // renderTitleValue function)
        buttonEdit: '=?',
        updateInDb: '=?',
        dontShowFilter: '=?',
        tooltip: '=?', // Used to render title on button
        mobilev: '=?',
        showTourneeBloq: '=?',
        filterHour: '=?', // Gives datetimepicker hour control
        geometricFeaturesHighlight: '=?', // Geometric Features are
        // highlighted
        displayFiltersOnInit: '=?',
        hideCrud: '=?',
        startCount: '=?',
        classicupdate: '=?',
        usesweetalert: '=?',
        chooseSeparator: '=?',
        configuser: '=?',
        specialShow: '=?',
        disabledEdit: '=?',
        disabledCheckbox: '=?',
        hideCheckbox: '=?', // Non affichage des cases de selection devant chaque élément
        saveConfig: '=?',
        fixHeader: '=?',
        supplementAttributes: '=?',
        keepSelectionOnPaginate: '=?',
        specialactions: '=?', // Actions au même niveau que les buttons
        // classique de update
        translateAttrs: '=?', // Attributes names to translate in render Value
        hideActions: '=?', // Hide objects actions
        hideAllEditButtons: '=?', // hide all actions and edit buttons
        doubleDecimalFromStringSize: '=?', // boolean : Nombre de decimal aprés
        // la virgule récupérer du
        // java.lang.double comme en db
        autoAdjustHeight: '=?', // Show all data and scroll go to page using
        // gcdatatable
        forceAllReloadAfterRemove: '=?', // Show all data and scroll go to
        // page using gcdatatable
        specialCassandraFilter: '=?', // Show all data and scroll go to page
        // using gcdatatable
        whereFilter: '=?', // Show all data and scroll go to page using
        // gcdatatable
        hideFilter: '=?', // Show all data and scroll go to page using
        // gcdatatable
        dontChangeCountAtFilterToogle: '=?', // Show all data and scroll go
        // to page using gcdatatable
        suppFuncRemove: '=?', // Show all data and scroll go to page using
        // gcdatatable
        showChart: '=?', // Affiche un bouton des graphiques
        hpoInfoNames: '=?', // Special informations HPO webservices
        dontUseGuid: '=?', // O_O !
        advancedFiltersConfiguration: '=?', // advanced string search,
        // startswith, contains, equals +
        // case sensitivty @TODO pour les
        // dates, ajouter cette année, ce
        // mois, ce jour
        isAncBacListeDossiers: '=?',
        preventToggleAllFromSelection: '=?',
        isRequeteurListe: '=?',
        isOmegaListe: '=?',
        canDeleteDossiers: '=?',
        removeFid: '=?',
        removeEditBtn: '=?',
        noSelection: '=?',
        displayFtiAlias: '=?', // affiche l'alias du fti dans l'entête
        showDeleteAll: '=?', // permet de vider le contenu de la table
        currentFormTemplate: '=?',
        tableClassList: '=?', // chaîne de classes CSS à ajouter à l'élément table du template HTML
        formType: '=?', // type de formulaire ('intervention_simple', 'form', 'table_simple'...)
        // -- Faut-il utiliser la classe de style "table-striped" pour les lignes du tableau ?
        tableStriped: '=?',
        // -- Doit être TRUE si le binding suivant (lineNgClass) est défini
        lineNgClassProvided: '@',
        // -- Fonction qui retourne le contenu pour l'attribut ngClass afin
        // -- de définir le style des lignes du tableau
        lineNgClass: '&',
        readOnly: '=?',
        doNotExportIds: '=?'
      },

      link: function (scope, element) {


        let renderIs;

        // -- Quand une hauteur est fournie, ne pas recalculer la hauteur automatiquement
        if (scope.autoAdjustHeight===undefined && scope.height!==undefined) {
          scope.autoAdjustHeight = false;
        }

        /**
         * Détermine le ng-class à utiliser et retourne le ng-class calculé.
         *
         * @param {*} obj Enregistrement = objet d'une ligne du tableau
         * @param {*} index Index de la ligne
         * @returns Le ngClass à utiliser
         */
        scope.tableLineClass = (obj,index) => {
          if (scope.lineNgClassProvided==='true') {
            // -- Un ngClass est configuré on l'utilise
            return scope.lineNgClass()(obj,index);
          }
          else {
            // -- Pas de ngClass particulier donc un utilise le ngClass standard
            return {'warning' : (scope.editableLines[index]
              || (scope.verifyupdatedProvided && scope.verifyupdated(obj))),
            'success' : scope.selectedLines[obj.id]==true,
            'danger': scope.verifyupdatedProvided && scope.verifydeleted(obj),
            'info': scope.verifyupdatedProvided
              && (scope.verifyissynchro(obj) ||  scope.verifyadded(obj)),
            'geometricFeaturesPositionning' : scope.geometricFeaturesHighlight};
          }
        };

        // datatable uniq identifier
        if (!scope.dontUseGuid) {
          scope.dataTableGuid = gaJsUtils.guid();
        }

        // force hide action label
        if (angular.isUndefined(scope.hideActLabel)) {
          scope.hideActLabel = true;
        }

        // force reset filter on toggle partout
        // @RB valide avec FRT/TB
        if (!angular.isDefined(scope.resetFilterOnToggle)) {
          scope.resetFilterOnToggle = true;
        }

        if (!angular.isDefined(scope.toggleAllGeojsonByDefault)) {
          scope.toggleAllGeojsonByDefault = true;
        }

        if (scope.$parent.selectedAttributes)
          scope.askedAttributes = scope.$parent.selectedAttributes;

        scope.dataupdated = false;
        scope.relationshow;

        let CurrentAppFactory = $rootScope.xgos.sector === 'bac' ? BacAppFactory : AncAppFactory;
        scope.extraColumn = CurrentAppFactory
        && CurrentAppFactory.appCfg
        && CurrentAppFactory.appCfg.main
        && CurrentAppFactory.appCfg.main.dossierCfg
        && CurrentAppFactory.appCfg.main.dossierCfg.extraColonne
          ? CurrentAppFactory.appCfg.main.dossierCfg.extraColonne : undefined;

        scope.canModifyFeature = false;

        // function to disable the modifying composant button
        const initUserRightToModifyFeature = () => {

          if (typeof scope.fti === 'object' && scope.fti !== null) {
            // KIS-3297 - IS: les tables d'objets cibles/sélectionnés sont des faux fti.
            // Les objets des tables de faux fti peuvent être modifiés
            // puisqu'ils sont une copie allégée des objets réels.
            // On distingue un faux-fti à la présence de la propriété fti.nameAliasMapping.

            if ($rootScope.xgos.user.login === 'root' || isFakeFti()) {
              scope.canModifyFeature = true;
            } else {
              RolesFactory.getRolesByUser($rootScope.xgos.user.login).then((res) => {
                if (Array.isArray(res.data)) {
                  // si on est rootUser ou sRootUser on doit pouvoir modifier tous les composants
                  const adminRole = res.data.find((role) => {
                    return role.name === 'rootUser' || role.name === 'sRootUser';
                  });
                  if (!adminRole) {
                    // si on est pas rootUser ou sRootUser
                    // On parcours les rôles
                    for (let role of res.data) {
                      // On parcourt les authorisations
                      for (let authorization of role.authorizations) {
                        // On contrôle les authorisations pour la couche concernée
                        if (authorization.item === scope.fti.uid) {
                          for (let attribut of authorization.subItems) {
                            if (attribut.write === true) {
                              scope.canModifyFeature = true;
                            }
                          }
                        }
                      }
                    }
                  } else {
                    scope.canModifyFeature = true;
                  }
                }
              });
            }
          }
        };

        scope.condfilter = false;
        scope.activestripped = true;
        var setTmpHeight = function () {
          $timeout(function () {
            var tmpHeight = gaJsUtils.getFillToBottomHeight(
              element.find('.gcDatatable')[0]
            );
            if (
              scope.exportCsv != '' ||
              angular.isDefined(scope.crud) ||
              scope.displayTotalNumber
            ) {
              tmpHeight -= 60;
            }
            scope.forceHeight = tmpHeight;
          });

          $(window).resize(function () {
            setTmpHeight();
          });
        };


        /**
         * Calcule la hauteur que l'on doit attribuer à la datatable en prenant
         * en compte les éléments environnants tels que la barre d'outils (toolbar)
         * la barre de titre, le bouton de retour, les éléments de l'onglet et
         * le bouton de redimensionnement.
         *
         * Cette méthode est appelée lorsque l'on est dans un onglet d'un
         * formulaire intervention simple.
         */
        const setDatatableHeightFromIsTab = () => {
          $timeout(() => {
            if (!datatableIsHelper.getParentTab(element[0])) {
              setDatatableHeightFromIsTab();
            }
            else {
              scope.forceHeight =
                renderIs.clientHeight - datatableIsHelper.tabSubpartHeightPadding(element[0])
                - datatableIsHelper.titleHeight(element[0])
                - datatableIsHelper.breadCrumbHeight(element[0])
                - datatableIsHelper.textsHeight(element[0])
                - datatableIsHelper.headerHeight(scope, element[0])
                - datatableIsHelper.bottomHeight(element[0])
                - 10; // 10 = hauteur du bottom resize
            }
          });
        };


        /**
         * Calcule et renvoie la hauteur ajustée de la table en tenant compte
         * des éléments environnants tels que la barre d'outils (toolbar) et
         * l'en-tête de la table (tableHeader),
         * afin d'assurer une bonne utilisation de l'espace disponible dans le conteneur de données.
         *
         * @returns {number|null} - La hauteur maximale disponible pour la table,
         *                        ou null si le conteneur de données n'est pas trouvé.
         */
        const getCleanTableHeight = () => {
          // Récupère l'élément de la table à partir du premier élément de la collection.
          const tableElement = element[0];

          // Recherche le conteneur parent avec l'ID 'donnees' qui contient les données de la table.
          const dataContainer = tableElement.closest('.map_panel.bottom');

          // Si le conteneur de données est trouvé, on continue le calcul.
          if (dataContainer) {
            // Récupère la hauteur disponible dans le conteneur de données.
            let maxHeight = dataContainer.clientHeight;

            // Recherche la barre d'outils (toolbar) dans le conteneur et soustrait sa hauteur.
            const toolbar = dataContainer.querySelector('.toolbar');
            if (toolbar) {
              // Ajuste la hauteur disponible en fonction de la barre d'outils.
              maxHeight -= toolbar.clientHeight;
            }

            // Soustrait la hauteur de l'en-tête de la table.
            maxHeight -= datatableIsHelper.headerHeight(scope, tableElement);

            // Retourne la hauteur maximale ajustée pour la table.
            // On respecte la règle CSS sur .fixedHeaderGlobalWrapper qui fixe la hauteur
            // minimale à 150px
            if (maxHeight < 150) {
              // Agrandit la hauteur du panel pour respecter une hauteur de table minimale de 150px
              dataContainer.style.height = dataContainer.clientHeight + (150 - maxHeight) + 'px';
            }
            return Math.max(maxHeight, 150);
          }

          // Si le conteneur de données n'est pas trouvé, renvoie null.
          return null;
        };


        // Try and set the height of the table so it fills the remaining space
        if (scope.tableFillPage === true) {
          setTmpHeight();
        } else {
          scope.$watch('height', function (h) {
            const cleanHeight = getCleanTableHeight();

            // A l'initialisation, la table prend la hauteur minimale fixée
            //par la variable height en input
            const isInitializing = scope.forceHeight === undefined;

            // Après initialisation, la hauteur de table est variable
            if (Number.isInteger(cleanHeight) && !isInitializing) {
              scope.forceHeight = cleanHeight;
            } else if (h) {
              // attention, c'est la merde initialement présente.
              // Ne prend pas en compte la présence de la barre d'outil ni de l'entêe.
              // KIS-3572: ne fonctionne pas lorsque le scroll vertical disparait
              scope.forceHeight = h;
            } else {
              // Remplace la règle CSS min-height: 320px
              // Cette règle CSS bloquait l'ajustement de la hauteur du panel
              $timeout(() => {
                renderIs = datatableIsHelper.getRenderInterventionSimle(element[0]);
                if (renderIs) {
                  setDatatableHeightFromIsTab();
                }
                else {
                  scope.forceHeight = 320;
                }
              });
            }
          });
        }

        if (!angular.isDefined(scope.dateFormat)) {
          scope.dateFormat = gaJsUtils.getLocaleDateString();
        }

        // init filter
        if (angular.isUndefined(scope.filtre)) {
          scope.filtre = false;
        }

        if (angular.isUndefined(scope.geojsondata)) {
          scope.geojsondata = false;
        }

        // common table params for fti or geojson data
        var _tableParams = {
          page: 1,
          count: scope.startCount || 10,
        };

        // --------------------------------------
        // specific CRUD
        // --------------------------------------
        if (
          angular.isDefined(scope.crud) &&
          (angular.isUndefined(scope.classicupdate) ||
            (angular.isDefined(scope.classicupdate) && !scope.classicupdate)) &&
          (angular.isUndefined(scope.hideAllEditButtons) ||
            (angular.isDefined(scope.hideAllEditButtons) &&
              !scope.hideAllEditButtons))
        ) {
          scope.noDefaultAction = true;

          scope.buttonactions = [
            {
              label: 'btn',
              btnclass: 'default',
              cfg: {
                iconOrLabel: 'label',
                label: 'common.edit',
                size: 'btn-xs',
              },
              config: { click: ['crud_update'] },
            },
          ];
        }

        /**
         * transfer the event to the external function if no crud object is
         * passed if there's a crud, use crud create, update delete functions
         *
         * @TODO should be able to add extra buttons even if there's a specific
         *       CRUD
         * @param args
         */
        scope.innerEvaluate = function (args) {
          if (angular.isDefined(scope.crud)) {
            var expr = angular.isArray(args.expr) ? args.expr[0] : args.expr;
            var action = expr.replace('crud_', '');
            $timeout(function () {
              scope.crud[action].func(angular.copy(args.obj));
            });
          } else {
            $timeout(function () {
              scope.evaluate(args);
            });
          }
        };

        /**
         * localise object on the map
         *
         * @param obj
         */
        scope.localisedata = (obj) => {
          mapJsUtils.localiseData(obj, scope.map);
        };

        /**
         * Toggle Edit mode for a specific line
         *
         * @param index
         */
        scope.editableLines = {};
        scope.toggleEdit = function (index) {
          if (Object.prototype.hasOwnProperty.call(scope.editableLines, index)) {
            delete scope.editableLines[index];
            reloadTable();
            scope.missingMandatoryAttributes = [];
          } else {
            scope.editableLines[index] = true;
          }
        };

        /**
         * Update the data
         *
         * @param obj
         * @param $index
         */
        scope.updateData = function (obj, $index) {
          if (
            !bizeditProvider.checkMandatory(
              scope,
              scope.displayAttributes,
              1,
              undefined,
              obj.properties
            )
          ) {
            return;
          }
          if (!scope.updateInDb) {
            if (scope.usesweetalert) {
              var callback = function (confirmed) {
                if (confirmed) {
                  var features = {};
                  features.type = 'FeatureCollection';
                  features.features = [];
                  features.features[0] = obj;
                  var srid = scope.map
                    ? scope.map.getView().getProjection().getCode()
                    : '';

                  EditFactory.update(scope.fti.uid, features, srid).then(
                    function () {
                      scope.toggleEdit($index);
                      AlertHpoFactory.getSimpleSuccess(
                        $filter('translate')('hpo.data.exchange.succes'),
                        $filter('translate')('hpo.data.exchange.updates'),
                        false
                      );
                    },
                    function () {
                      AlertHpoFactory.getSimpleFail(
                        $filter('translate')('hpo.data.exchange.fail'),
                        $filter('translate')('hpo.data.exchange.updatesfail'),
                        true
                      );
                    }
                  );
                }
              };
              AlertHpoFactory.alertConfirmCallback(
                $filter('translate')('hpo.common.warning'),
                $filter('translate')('datatable.confirm_update'),
                'warning',
                true,
                undefined,
                '#F50072',
                true,
                undefined,
                undefined,
                true,
                true,
                callback
              );
            } else {
              var ans = confirm(
                $filter('translate')('datatable.confirm_update')
              );
              if (ans) {
                var features = {};
                features.type = 'FeatureCollection';
                features.features = [];
                features.features[0] = obj;
                var srid = scope.map
                  ? scope.map.getView().getProjection().getCode()
                  : '';

                EditFactory.update(scope.fti.uid, features, srid).then(
                  function () {
                    scope.toggleEdit($index);
                  }
                );
              }
            }
          } else {
            scope.toggleEdit($index);
          }
        };

        /**
         * Add a new editable line to the datable
         */
        scope.newLines = [];
        scope.addNewLine = function () {
          var newLine = {};
          scope.displayAttributes.forEach(function (att) {
            newLine[att.name] = '';
          });
          scope.newLines.push(newLine);
        };

        scope.objectKeys = function (obj) {
          return Object.keys(obj);
        };

        scope.runActions = function (action, obj) {
          eval(action(obj));
        };

        /**
         * add new data to the feature
         *
         * @param obj
         * @param lineIndex
         */
        scope.addData = function (obj, lineIndex) {
          var features = {};
          features.type = 'FeatureCollection';
          features.features = [];
          features.features[0] = {};
          features.features[0].type = 'feature';
          features.features[0].properties = obj;
          features.features[0].geometry = {};
          if (angular.isDefined(scope.map)) {
            var srid = scope.map.getView().getProjection().getCode();
          } else {
            var srid = '';
          }

          EditFactory.add(scope.fti.uid, features, srid).then(function (res) {
            if (res.data.errors.length > 0) {
              require('toastr').error(res.data.errors[0]);
            }
            if (res.data.create.length > 0) {
              scope.newLines.splice(lineIndex, 1);
              reloadTable();
            }
          });
        };


        scope.removeselecteddata = () => {
          // wont work for geojson data
          var ids = scope.res.map(x => x.id);
          removedata(false,ids);
          // reset la selection
          scope.res = [];
        };

        scope.removeAllData = () => {
          removedata(true);
          // reset la selection
          scope.res = [];
        };


        scope.$on('updateFromConfigCtrl', function () {
          scope.keepSelectionOnPaginate = true;
        });

        /**
         * Remove data
         *
         * @param id
         * @param index
         */
        let removedata = (allData,id) => {
          var resultat = angular.copy(scope.res);
          if (scope.usesweetalert) {
            var callback = function (confirmed) {
              if (confirmed) {
                if (
                  scope.crud &&
                  angular.isDefined(scope.crud.remove) &&
                  typeof scope.crud.remove === 'object' &&
                  Object.prototype.hasOwnProperty.call(scope.crud.remove, 'func')
                ) {
                  scope.crud.remove.func(scope.fti.name, id);
                  if (scope.suppFuncRemove){
                    scope.suppFuncRemove(resultat);
                  }
                } else {
                  gaDomUtils.showGlobalLoader();
                  EditFactory.remove(scope.fti.uid, id, allData).then(() =>{
                    gaDomUtils.hideGlobalLoader();
                    if (scope.geojsondata != false) {
                      scope.removefromlist(0);
                    } else {
                      scope.tableParams.reload();
                    }

                    if (angular.isDefined(scope.map)) {
                      gclayers.refreshlayerByid(scope.fti.uid, scope.map);
                    }

                    if (scope.suppFuncRemove){
                      scope.suppFuncRemove(resultat);
                    }

                    AlertHpoFactory.getSimpleSuccess(
                      $filter('translate')('hpo.data.exchange.succes'),
                      $filter('translate')('hpo.data.exchange.suppresse'),
                      false
                    );
                    if (scope.forceAllReloadAfterRemove)
                      $rootScope.$broadcast('reloadDatatable');
                  }, () => {
                    gaDomUtils.hideGlobalLoader();
                    AlertHpoFactory.getSimpleFail(
                      $filter('translate')('hpo.data.exchange.fail'),
                      $filter('translate')('hpo.data.exchange.suppressefail'),
                      true
                    );
                  }
                  );
                }
              }
            };
            AlertHpoFactory.alertConfirmCallback(
              $filter('translate')('hpo.common.warning'),
              $filter('translate')(allData?'datatable.confirm_delete_All':'datatable.confirm_delete'),
              'warning',
              true,
              undefined,
              '#F50072',
              true,
              undefined,
              undefined,
              true,
              true,
              callback
            );
          } else {
            var ans = confirm($filter('translate')(allData?'datatable.confirm_delete_All':'datatable.confirm_delete'));
            if (ans) {
              gaDomUtils.showGlobalLoader();
              EditFactory.remove(scope.fti.uid, id, allData).then(() =>{
                gaDomUtils.hideGlobalLoader();
                if (scope.geojsondata != false) {
                  scope.removefromlist(0);
                } else {
                  scope.tableParams.reload();
                }

                if (scope.suppFuncRemove){
                  scope.suppFuncRemove(resultat);
                }

                if (angular.isDefined(scope.map)) {
                  gclayers.refreshlayerByid(scope.fti.uid, scope.map);
                }
              },()=>{
                gaDomUtils.hideGlobalLoader();
              });
            }
          }
        };

        /**
         * Remove data only from the list (geojsondatamode)
         *
         * @param obj
         * @param index
         */
        scope.removefromlist = function (obj, index) {
          scope.geojsondata.features.splice(index, 1);
          if (scope.selectedLines && obj && angular.isDefined(scope.selectedLines[obj.id])) {
            delete scope.selectedLines[obj.id];
          }

          scope.geojsondata.totalFeatures = scope.geojsondata.totalFeatures - 1;
          scope.data = scope.geojsondata.features;
          if (scope.tableParams && typeof scope.tableParams.reload === 'function') {
            scope.tableParams.reload();
          }
          setResultat();
        };

        var setResultat = function () {
          var res = [];
          angular.forEach(scope.data, function (d) {
            if (
              scope.selectedLines[d.id] == true &&
              !res.filter((resDossier) => resDossier.id === d.id).length
            ) {
              res.push(d);
            }
          });
          if (scope.keepSelectionOnPaginate) {
            if(angular.isDefined(scope.res) && scope.res!==false && scope.res.forEach){
              scope.res.forEach(function (dossier) {
                if (
                  res.indexOf(dossier) == -1 &&
                  !res.filter((resDossier) => resDossier.id === dossier.id).length
                ) {
                  res.push(dossier);
                }
              });
              angular.forEach(scope.data, function (first) {
                angular.forEach(scope.res, function (seconde) {
                  if (first.id == seconde.id) {
                    if (scope.selectedLines[first.id] == undefined) {
                      scope.selectedLines[first.id] = true;
                    }
                  }
                });
              });
            }
          }
          scope.res = res;
          scope.totalSelectedElementsNumber = Object.keys(
            scope.selectedLines
          ).length;
        };

        scope.selectedLines = {};
        scope.allLinesSelected = false;
        scope.totalSelectedElementsNumber = 0;
        scope.toggleFromSelection = function (obj, force) {
          if (force || !scope.res) {
            scope.selectedLines = {};
            scope.res = [];
          }

          var found = -1;
          if (scope.res != undefined) {
            found = scope.res
              .map(function (o) {
                return o.id;
              })
              .indexOf(obj.id);
          }

          if (found != -1) {
            if (scope.keepSelectionOnPaginate) scope.res.splice(found, 1);
            delete scope.selectedLines[obj.id];
          } else {
            scope.selectedLines[obj.id] = true;
          }

          // select only one element
          if (scope.singleSelection) {
            if (found != -1) {
              scope.selectedLines = {};
            } else {
              scope.selectedLines = {};
              scope.selectedLines[obj.id] = true;
            }
          } else {
            if (found != -1) {
              delete scope.selectedLines[obj.id];
            } else {
              scope.selectedLines[obj.id] = true;
            }
          }

          // reset variables autres que 'current' au clic sur le bouton Edit (sur IS uniquement)
          if (scope.formType === 'intervention_simple') {
            scope.$emit('editFormLine',true);
          }

          setResultat();
        };

        /**
         * toggleAllFromSelection
         */
        scope.toggleAllFromSelection = function (force) {
          if (angular.isDefined(force)) {
            scope.allLinesSelected = !force;
          }
          if (scope.allLinesSelected || scope.isOmegaListe) {
            scope.selectedLines = {};
            scope.res = [];
          } else {
            scope.selectedLines = [];
            angular.forEach(scope.data, function (d) {
              scope.selectedLines[d.id] = true;
            });
          }
          scope.allLinesSelected = !scope.allLinesSelected;
          setResultat();
        };

        /**
         * En attendant une vraie selection cross-page il faut reset le
         * selectlines a seulement ce qui est visible dans la vue en cours, ce
         * qui peut changer si on met un filtre ou change de page
         */
        var resetSelectLines = function () {
          var check = scope.data.map(function (x) {
            return x.id;
          });
          for (var i in scope.selectedLines) {
            if (check.indexOf(i) == -1) delete scope.selectedLines[i];
          }
          setResultat();
        };

        scope.getDataLength = function () {
          return (
            scope.tableParams &&
            scope.tableParams.data &&
            scope.tableParams.data.length > 0
          );
        };

        // ------------------------
        // DataTable initialisation
        // ------------------------

        if (angular.isDefined(scope.ftid) && angular.isUndefined(scope.fti)) {
          scope.fti = FeatureTypeFactory.getFeatureByUid(scope.ftid);
        }

        /*
         * check relations
         */

        if (
          scope.geoj == undefined &&
          scope.fti &&
          scope.fti.relations &&
          scope.fti.relations.length != 0
        ) {
          var relations = scope.fti.relations
            .map(function (x) {
              if (x.type != 'REL_WS' && x.idEnd) return x.idEnd;
            })
            .filter(function (x) {
              if (x != undefined) {
                return x;
              }
            });
          scope.ftis = [];
          relations.map(function (x) {
            var fti = FeatureTypeFactory.getFeatureByUid(x);
            if (
              fti !== null &&
              fti.geographic &&
              scope.ftis
                .map(function (x) {
                  return x.uid;
                })
                .indexOf(fti.uid) === -1
            )
              scope.ftis.push(fti);
          });
        }

        /**
         * Mettre a jour la table en fonction du fti relation
         */
        scope.updateTable = function (fti) {
          scope.where = '';
          scope.whereFilter = '';
          $rootScope.$broadcast('initFilterTemp', '');

          if (scope.askedAttributes) scope.attributes = fti.askedAttributes;
          else scope.attributes = fti.attributes;
          scope.geojsondata = false;
          var ftiorigin = undefined;
          if (
            fti.relations
              .map(function (x) {
                return x.idEnd;
              })
              .indexOf(scope.fti.uid) == -1
          )
            ftiorigin = angular.copy(scope.fti);
          scope.where = '';

          getAttributeTypes();

          scope.fti = fti;

          var relations = scope.fti.relations
            .map(function (x) {
              if (x.type != 'REL_WS' && x.idEnd) return x.idEnd;
            })
            .filter(function (x) {
              if (x != undefined) {
                return x;
              }
            });
          scope.ftis = [];
          relations.map(function (x) {
            var fti = FeatureTypeFactory.getFeatureByUid(x);
            if (
              fti !== null &&
              fti.geographic &&
              scope.ftis
                .map(function (x) {
                  return x.uid;
                })
                .indexOf(fti.uid) === -1
            )
              scope.ftis.push(fti);
          });
          if (
            angular.isDefined(ftiorigin) &&
            scope.ftis
              .map(function (x) {
                return x.uid;
              })
              .indexOf(fti.uid) === -1
          )
            scope.ftis.push(ftiorigin);
          scope.tableParams.reload();
          var grouplayers = gclayers.getGroupLayer();
          var bool = true;
          angular.forEach(grouplayers, function (val) {
            if (bool)
              angular.forEach(val, function (value) {
                if (bool) {
                  if (
                    angular.isDefined(value.get('fti')) &&
                    value.get('fti').uid == scope.fti.uid
                  ) {
                    $rootScope.$broadcast('changeLayerManagerCurrlayer', value);
                    bool = false;
                  }
                }
              });
          });
        };

        scope.canDeleteDossiers = () => {
          if (($rootScope.xgos.isadmin || Array.isArray($rootScope.xgos.user.roles)) && ($rootScope.xgos.sector === 'anc' || $rootScope.xgos.sector === 'bac')) {
            return scope.res.length > 0 && ( $rootScope.xgos.isadmin || $rootScope.xgos.user.roles.some(x => x.name === 'BAC_responsable' || x.name === 'ANC_responsable'));
          } else if (!scope.res) {
            return false;
          } else if (Array.isArray(scope.res)) {
            return scope.res.length > 0;
          } else {
            return false;
          }

        };


        /**
         * Allow Read / Write on every attribute
         */
        var setAllowReadWrite = function (attributes) {
          scope.userRights = {};

          // KIS-2339 / KIS-2823 / KIS-3297: dans le cas des objects cibles/sélectionnés on crée un fake fti (puisque c'est une couche composé pas une vraie couche)
          // dans le cas de cette couche l'utilisateur a des droits équivalent à un admin sur cette couche
          let isAdmin =
            $rootScope.xgos.isroot || $rootScope.xgos.isadmin ||
            ($rootScope.xgos.sector === 'hpo' &&
              $rootScope.xgos.user.roles.some(
                userRole => userRole.name === 'HpoExpertClient')) || isFakeFti();

          scope.allowDeleteUpdate = isAdmin;

          if (Array.isArray(attributes)) {
            for (const attr of attributes) {
              if (attr) {
                scope.userRights[attr.name] = {
                  read: scope.usemode === 'query' ? true : isAdmin,
                  write: isAdmin,
                };

                // si attribut ne fait pas partie des attributs du featuretypeinfo,
                // c'est un extra (par exemple proprietaires dans anc)
                // on affiche toujours ce genre
                if (angular.isDefined(scope.fti.attributes)) {
                  const found = scope.fti.attributes.some(ftiAttr => ftiAttr.name === attr.name);

                  if (!found) {
                    scope.userRights[attr.name] = {
                      read: true,
                      write: true,
                    };
                  }
                }
              }
            }
          }

          if (!isAdmin || scope.geojsondata !== false) {
            $rootScope.xgos.user.roles.forEach(function (role) {
              role.authorizations.forEach(function (authorization) {
                if (authorization.item == scope.fti.uid) {
                  authorization.subItems.forEach(function (sub) {
                    scope.userRights[sub.item] = {
                      read:
                      scope.userRights[sub.item] == undefined ||
                      !scope.userRights[sub.item].read
                        ? sub.read
                        : true,
                      write:
                        scope.userRights[sub.item] == undefined ||
                        !scope.userRights[sub.item].write
                          ? sub.write
                          : true,
                    };
                    if (sub.read || sub.write) scope.allowDeleteUpdate = true;
                  });
                }
              });
            });

            $rootScope.xgos.user.groups.forEach(function (group) {
              group.roles.forEach(function (inRole) {
                inRole.authorizations.forEach(function (inAuthorization) {
                  if (inAuthorization.item == scope.fti.uid) {
                    inAuthorization.subItems.forEach(function (inSub) {
                      scope.userRights[inSub.item] = {
                        read:
                          scope.userRights[inSub.item] == undefined ||
                          !scope.userRights[inSub.item].read
                            ? inSub.read
                            : true,
                        write:
                          scope.userRights[inSub.item] == undefined ||
                          !scope.userRights[inSub.item].write
                            ? inSub.write
                            : true,
                      };
                      if (inSub.read || inSub.write)
                        scope.allowDeleteUpdate = true;
                    });
                  }
                });
              });
            });
          }
        };

        /**
         * getAttributeTypes
         */
        const getAttributeTypes = (keepFilters) => {
          keepFilters = keepFilters || false;

          scope.displayAttributesType = {};

          var refAttributes = [];
          if (
            angular.isDefined(scope.attributes) &&
            typeof scope.attributes[0] != 'string'
          ) {
            refAttributes = scope.attributes;
            if (scope.supplementAttributes)
              refAttributes = refAttributes.concat(scope.supplementAttributes);
          } else {
            for (var i in scope.attributes) {
              for (var j in scope.fti.attributes) {
                if (scope.attributes[i] == scope.fti.attributes[j].name) {
                  refAttributes.push(angular.copy(scope.fti.attributes[j]));
                  break;
                }
              }
            }
          }

          if (angular.isArray(refAttributes)) {
            refAttributes.map(function (a) {
              var fieldType = gaJsUtils.getSimpleAttributeType(a);

              var displayKey = false;

              if (fieldType == 'ObjectsArray') displayKey = a.key;

              scope.displayAttributesType[a.name] = {
                type: a.type,
                fieldType: fieldType,
                filterType: a.filterType || 'default',
              };

              if (displayKey) {
                scope.displayAttributesType[a.name].key = displayKey;
              }
            });
          }

          if (!keepFilters) setDefaultFilters();
        };

        scope.isArray = angular.isArray;

        /** fti changes * */
        scope.dataOrigin = '';

        // enregistre les parametres des requetes pour utilisation dans l'export
        // csv par ex
        var extraWebService = false,
          extraWebServiceParameters = false,
          webServiceParameters = [];


        /**
         * Chercher les données depuis un service qui n'est pas
         * le service KIS par défaut.
         *
         * params: description des paramétres du service
         * $defer: promesse à résoudre
         */
        const getDataOfDatatable4Ews = (params, $defer) => {
          extraWebService.then(
            (data) => {
              gaDomUtils.hideGlobalLoader();
              scope.requestSent = true;
              params.total(data.data.totalFeatures);
              scope.totalElementNumber = data.data.totalFeatures;
              scope.data = data.data.features;
              $defer.resolve(data.data.features);
              if (!scope.data.length && params.page() != 1) {
                params.page(1);
                scope.tableParams.reload();
              }
              resetSelectLines();
              $rootScope.$broadcast('dataTable_DataLoaded', {
                table_guid: scope.dataTableGuid,
              });
            },
            () => {
              gaDomUtils.hideGlobalLoader();
            }
          );
        };


        /**
         * Fonction pour simplifier l'écriture de lignes de code un peu longues.
         *
         * @param {*} eltDesc: information d'identification de l'élément cherché
         *
         * @returns élémnt HTML du DOM
         */
        const getAngElt = (eltDesc) => {
          return angular.element(eltDesc);
        };


        const doTheQueryDataGotData = (data, params, $defer) => {
          scope.requestSent = true;
          if (angular.isDefined(data.data.realTotalFeatures)) {
            params.total(data.data.realTotalFeatures);
            scope.totalElementNumber = data.data.realTotalFeatures;
          } else {
            params.total(data.data.totalFeatures);
            scope.totalElementNumber = data.data.totalFeatures;
          }
          scope.data = data.data.features;
          if (angular.isDefined(scope.data) && scope.data.length > 0 &&
                angular.isDefined(scope.attributesToRound) &&
                scope.attributesToRound.length > 0
          ) {
            scope.data.forEach(function (attr) {
              scope.attributesToRound.forEach(function (attrRond) {
                attr.properties[attrRond] = Math.round(
                  attr.properties[attrRond]
                );
              });
            });
          }
          $defer.resolve(data.data.features);
          if (angular.isDefined(scope.data) && !scope.data.length
                && params.page() != 1) {
            params.page(1);
            scope.tableParams.reload();
          }
          $rootScope.$broadcast('dataTable_DataLoaded', {
            table_guid: scope.dataTableGuid,
          });
          var reloaded = 0;
          var stop;
          if (scope.autoAdjustHeight) {
            stop = $interval(function () {
              if (
                getAngElt('.fixInner .table.table-striped').height() > 0
                  || reloaded > 100
              ) {
                scope.height =
                    getAngElt('.fixInner .table.table-striped').height() +
                    getAngElt('.fixInner [ng-table-pagination]').height() +
                    getAngElt('.fixThWrapper').height() + 50 + 'px';
                $interval.cancel(stop);
              } else {
                reloaded++;
              }
            }, 100);
          }
        };

        /**
         * Récupére les objets du datatable avec la QueryFactory de KIS.
         *
         * @param {*} wsParams: paramétre du web service défini par nous
         * @param {*} params: objet rçu du composant ngtable de base
         * @param {*} $defer: gestion de promesse rçu du composant
         *                    ngtable de base
         */
        const doTheQueryData = (wsParams, params, $defer) => {

          QueryFactory.data(scope.fti.uid, wsParams.filter, wsParams.crs,
            wsParams.page, wsParams.count, scope.sort_by_title
          ).then(
            (data) => {
              doTheQueryDataGotData(data, params, $defer);
              scope.prevQueryDataParams.result = data;
              gaDomUtils.hideGlobalLoader();
            },
            () => {
              gaDomUtils.hideGlobalLoader();
            }
          );
        };


        /**
         * Vérifie qu'il faut faire la requête HTTP pour récupérer
         * les données de la datatable.
         * Il ne faut pas la faire si on vient de la faire
         * avec les mêmes paramétres.
         *
         * @param {*} params
         * @returns
         */
        const haveToDoTheQueryData = (params) => {
          let now = new Date();

          //-- Première requête pour ce datatable.
          if (!scope.prevQueryDataParams) {
            scope.prevQueryDataParams = {};
            scope.prevQueryDataParams = angular.copy(webServiceParameters);
            scope.prevQueryDataParams.when = now.getTime();
            return true;
          }

          //-- Requête différente de la précédente pour ce datatable.
          if (params.uid !== scope.prevQueryDataParams.uid
            || params.filter !== scope.prevQueryDataParams.filter
            || params.crs !== scope.prevQueryDataParams.crs
            || params.count !== scope.prevQueryDataParams.count
            || params.page !== scope.prevQueryDataParams.page
            || params.sort !== scope.prevQueryDataParams.sort
          ) {
            scope.prevQueryDataParams = angular.copy(webServiceParameters);
            return true;
          }

          //-- Requête identique à la précédente pour ce datatable et ce
          //-- dans un intervale inférieur à 2 secondes.
          if (now.getTime() - scope.prevQueryDataParams.when < 100) {
            return false;
          }

          //-- Même requête que la précédente mais dans un délai
          //-- suffisament long pour devoir l'exécuter à nouveau.
          scope.prevQueryDataParams.when = now.getTime();
          return true;
        };


        /**
         * -1- Prépare la requête HTTP à destination du serveur KIS
         *     pour récupérer les données du datatable.
         * -2- Vérifie que l'on n'a pas déjà exécuter à l'instant cette requête.
         * -3- Si tel n'est pas le cas on l'exécute pour mettre
         *     les données dans le datatable.
         *
         * @param {*} params
         * @param {*} $defer
         * @param {*} filter
         * @param {*} crs
         */
        const getDataOfDatatableQueryData = (params, $defer, filter, crs) => {
          if (filter == '' && scope.condfilter == true) {
            filter = '1=10';
          }

          if (typeof AppAndroid !== 'undefined')
            filter = filter.replace('ILIKE', 'LIKE');

          webServiceParameters = {
            uid: scope.fti.uid,
            filter: filter,
            crs: crs,
            count: params.count(),
            page: params.page(),
            sort: scope.sort_by_title,
          };

          if (haveToDoTheQueryData(webServiceParameters)) {
            doTheQueryData(webServiceParameters, params, $defer);
          } else {
            gaDomUtils.hideGlobalLoader();
          }
        };


        const getDataOfDatatable = (params, $defer, filter, crs) => {

          if (extraWebService) {
          // another webservice is used to retrieve the data
            getDataOfDatatable4Ews (params, $defer);
          } else {
            // classic data webservice
            getDataOfDatatableQueryData (params, $defer, filter, crs);
          }
        };


        scope.$watch('fti', function (fti) {
          if (!angular.isDefined(fti) || fti == null) return;

          if (angular.isDefined(scope.tableParams)) {
            getAttributeTypes(1);
            scope.tableParams.reload();
          } else {
            if (angular.isUndefined(scope.attributes)) {
              scope.attributes = scope.fti.attributes;
            }

            // set tableParams only when the data comes from a FeatureType
            if (scope.geojsondata === false) {
              getAttributeTypes();

              scope.tableParams = new ngTableParams(_tableParams, {
                total: 0,
                getData: function ($defer, params) {
                  $rootScope.$broadcast('gcDataTableGetDataFti', { params });
                  gaDomUtils.showGlobalLoader();
                  var crs = angular.isDefined(scope.map)
                    ? scope.map.getView().getProjection().getCode()
                    : '';

                  var filter = '';

                  if (!angular.isUndefined(scope.where)) filter = scope.where;

                  if (scope.whereFilter != '') {
                    if (filter != '') filter += ' AND ';
                    filter += ' ' + scope.whereFilter;

                    if (typeof filter === 'string' && filter !== '1=1')  {
                      // KIS-3188: Usage des filtres - condition EGAL et
                      // non CONTIENT par défaut des champs restriction sur table
                      filter = removeWildcardIfRestrictedKey(filter);
                    }
                  }

                  scope.fullFilter = filter;

                  if (typeof scope.datawebservice === 'function') {
                    extraWebServiceParameters = {
                      params: {
                        uid: scope.fti.uid,
                        filter: filter,
                        crs: crs,
                        page: params.page(),
                        count: params.count(),
                        liaisonFilter: scope.liaisonFilter,
                        case_sensitive: scope.sensible.case,
                        sort: scope.sort_by_title,
                      },
                    };

                    try {
                      extraWebService = scope.datawebservice(
                        extraWebServiceParameters
                      );
                    } catch (e) {
                      // Handle the exception here
                      console.error(e);
                    }
                  }

                  getDataOfDatatable (params, $defer, filter, crs);
                },
              });

              scope.dataOrigin = 'fti';
            }
          }
        });

        // ------------------------
        // Update on ftid/data/where change
        // ------------------------
        function reloadTable() {
          if (angular.isDefined(scope.tableParams)) scope.tableParams.reload();
        }

        scope.$watch('ftid', function (ftid) {
          if (angular.isDefined(ftid)) {
            scope.fti = FeatureTypeFactory.getFeatureByUid(scope.ftid);
          }
        });

        var reloadAttributes = function (attributes) {
          let tmpAttributes = [];
          if (angular.isUndefined(attributes)) {
            tmpAttributes = Array.isArray(scope.fti.attributes) ? scope.fti.attributes : [];
          } else {
            // attributes is an array of string
            if (typeof attributes[0] == 'string') {
              let ind;
              if (Array.isArray(scope.fti.attributes)) {
                scope.fti.attributes.forEach(function (attr) {
                  ind = attributes.indexOf(attr.name);
                  if (ind != -1) {
                    tmpAttributes[ind] = attr;
                  }
                });
              }
            } else {
              tmpAttributes = attributes;
            }
          }

          setAllowReadWrite(tmpAttributes);

          // remove attributes which aren't allowed to be read
          scope.displayAttributes = [];
          tmpAttributes.forEach(function (attr) {
            if (typeof AppAndroid !== 'undefined') {
              scope.displayAttributes.push(attr);
            } else {
              if (scope.userRights[attr.name].read) {
                scope.displayAttributes.push(attr);
              }
            }
          });

          if (scope.supplementAttributes)
            scope.displayAttributes = scope.displayAttributes.concat(
              scope.supplementAttributes
            );
        };

        scope.$watch('attributes', function (attributes) {
          if (!angular.isDefined(scope.fti) || scope.fti == null) return;

          reloadAttributes(attributes);
        });

        /**
         * Formate les nombres dans l'objet scope.geojsondata.features.
         * Récupére depuis scope.displayAttributes les attributs de type 'double' ou 'float'.
         * Ensuite, il itère au sein de l'array scope.geojsondata.features et
         * pour chaque feature, il formate les valeurs les attributs de type réels
         * afin de ne gardez que 3 décimales.
         *
         * @return {void} Cette fonction ne renvoie aucune valeur.
         */
        const formatNumbers = () => {
          const fieldsToFormat = [];
          if (scope.displayAttributes) {
            for (const attr of scope.displayAttributes) {
              const type = attr.type.toLowerCase();
              if (type.includes('double') || type.includes('float')) {
                fieldsToFormat.push(attr.name);
              }
            }
          }
          for (const feature of scope.geojsondata.features) {
            for (const attrName of fieldsToFormat) {
              feature.properties[attrName]
                = Math.round(Number.parseFloat(feature.properties[attrName])*1000)/1000;
            }
          }
        };


        const tableBodyHeight = () => {
          return angular.element('.fixInner .table.table-striped').height()
            ? angular.element('.fixInner .table.table-striped').height()
            : angular.element('.fixedHeaderTableWrapper').height();
        };


        // Some geojsondata is passed to the directive
        scope.$watch('geojsondata', (geojsondata) => {
          if (angular.isDefined(geojsondata) && geojsondata!== null && geojsondata !== false
            && geojsondata != '' && angular.isDefined(geojsondata.features)
            && geojsondata.totalFeatures != 0) {

            scope.data = geojsondata.features;

            // first init
            if (!angular.isDefined(scope.tableParams)) {
              getAttributeTypes();
              formatNumbers();

              scope.tableParams = new ngTableParams(_tableParams, {
                total: scope.data.length, // length of data
                getData: function ($defer, params) {
                  // filter...
                  if (scope.geojsondata===false) {
                    return;
                  }
                  let _data = Array.isArray(scope.geojsondata.features) ?
                    scope.geojsondata.features.filter(scope.listFilter.geojson())
                    : [];
                  // and sort...
                  if (angular.isDefined(currSortData.column)) {
                    var sorted = gaJsUtils.sortArrayOfFeaturesByKey(
                      _data,
                      currSortData.column,
                      scope.displayAttributesType[currSortData.column].fieldType
                    );
                    _data =
                      currSortData.order == 'asc' || currSortData.order == ''
                        ? sorted
                        : sorted.reverse();
                  }

                  scope.data = _data;
                  params.total(_data.length);
                  scope.totalElementNumber = _data.length;
                  if (scope.autoAdjustHeight && !scope.heightAdjustmentIsActive
                    && !scope.isHeightCalculated) {
                    scope.heightAdjustmentIsActive = true;
                    let reloaded = 0;
                    const stop = $interval(() => {
                      if (tableBodyHeight() > 0 ||  reloaded > 25) {
                        scope.height =
                          tableBodyHeight()
                          + angular.element('.fixInner [ng-table-pagination]').height()
                          + angular.element('.fixThWrapper').height()
                          + 50 + 'px';
                        $interval.cancel(stop);
                        scope.heightAdjustmentIsActive = false;
                        scope.isHeightCalculated = true;
                      } else {
                        reloaded++;
                      }
                    }, 500);
                  }
                  $defer.resolve(
                    scope.data.slice(
                      (params.page() - 1) * params.count(),
                      params.page() * params.count()
                    )
                  );
                },
              });

              scope.dataOrigin = 'geojson';
            } else {
              getAttributeTypes(1);
              reloadTable();
            }

            // make everything selected
            if (!scope.preventToggleAllFromSelection)
              scope.toggleAllFromSelection(1);
          }
        }, true);

        // Where clauses are modified
        scope.$watch('where', function (where, oldwhere) {
          if (
            angular.isDefined(where) &&
            typeof where === 'string' &&
            where.trim() != ''
          ) {
            if (where != oldwhere) {
              reloadTable();
            }
          }
        });

        scope.$watch('whereFilter', function (where, oldwhere) {
          if (angular.isDefined(where) && typeof where === 'string') {

            if (typeof where === 'string' && where.length > 0 && where !== '1=1') {
              // KIS-3188: Usage des filtres - condition EGAL et non CONTIENT par défaut
              //des champs restriction sur table
              where = removeWildcardIfRestrictedKey(where);
            }

            if (where != oldwhere) {
              reloadTable();
            }
          }
        });

        scope.$watch('liaisonFilter', function (where, oldwhere) {
          if (
            angular.isDefined(where) &&
            typeof where === 'string' &&
            where.trim() != '' &&
            oldwhere.trim() != ''
          ) {
            reloadTable();
          }
        });

        scope.criterias = {};
        scope.searchMode = false;
        scope.svgPage = 1;

        /**
         * setDefaultFilters
         */
        var setDefaultFilters = function () {
          scope.criterias = {};
          for (var i in scope.displayAttributesType) {
            if (
              scope.displayAttributesType[i].fieldType == 'date' ||
              scope.displayAttributesType[i].fieldType == 'number'
            ) {
              scope.criterias[i] = { symbol: '=' };
            }
          }
          // add criteria for fid
          scope.criterias.fid = { symbol: '=' };
          if (scope.geojsondata && scope.where){
            const andFilters = scope.where.split('&&');
            if(andFilters.length>0){
              for (const andFilter of andFilters) {
                const [key, value] = andFilter.split('=');
                if (value) {
                  scope.criterias[key] = { value };
                }
              }
            }
          }
        };

        /** display / hide full list when search mode is activated * */
        scope.toggleSearchMode = function () {
          if (scope.searchMode && scope.resetFilterOnToggle) {
            setDefaultFilters();
            scope.sort_title_style = {};
            scope.sort_by_title = '';
            currSortData = {};
          }

          scope.searchMode = !scope.searchMode;
          if (scope.dataOrigin == 'geojson') {
            if (scope.searchMode) {
              scope.svgPage = scope.tableParams.page();
              // scope.tableParams.count(scope.tableParams.total());
              if (scope.dontChangeCountAtFilterToogle) {
                scope.tableParams.count(_tableParams.count);
                scope.tableParams.page(scope.svgPage);
              } else {
                scope.tableParams.count(100);
              }
            } else {
              scope.tableParams.count(_tableParams.count);
              scope.tableParams.page(scope.svgPage);
            }
          }
        };

        if (scope.displayFiltersOnInit == true) {
          $timeout(function () {
            scope.toggleSearchMode();
          });
        }

        /**
         * Client side filtering for geojson data fti filter always returns true
         * as the filtering is handled by the where clause
         */
        scope.listFilter = {
          geojson: function () {
            return function (item) {
              let showItem = true;
              scope.fullFilter = scope.criterias;
              for (var index in scope.criterias) {
                if (!showItem) {
                  return false;
                }
                var c = scope.criterias[index],
                  value = c.value,
                  symbol = c.symbol;

                if (value) {
                  //si pas de valeur dans cette ligne alors on affiche pas la ligne
                  if (item.properties[index] != undefined) {
                    // date
                    if (scope.displayAttributesType[index] &&
                      scope.displayAttributesType[index].fieldType
                    ) {
                      if (
                        scope.displayAttributesType[index].fieldType == 'date'
                      ) {
                        var dateCmp = new Date(value);
                        var dateLigne = new Date(item.properties[index]);
                        if (dateCmp.getTime() > 0 && dateLigne.getTime() > 0) {
                          console.log(symbol);
                          if (symbol == '=')
                            showItem = dateLigne.getTime() == dateCmp.getTime();
                          if (symbol == '>')
                            showItem = dateLigne.getTime() > dateCmp.getTime();
                          if (symbol == '<')
                            showItem = dateLigne.getTime() < dateCmp.getTime();
                          if (symbol == '>=')
                            showItem = dateLigne.getTime() >= dateCmp.getTime();
                          if (symbol == '<=')
                            showItem = dateLigne.getTime() <= dateCmp.getTime();
                          if (symbol == 'between') {
                            var dateMax = new Date(c.valuemax);
                            if (dateMax.getTime() > 0) {
                              showItem =
                                dateLigne.getTime() >= dateCmp.getTime() &&
                                dateLigne.getTime() <= dateMax.getTime();
                            }
                          }
                        }
                      } else if (
                        scope.displayAttributesType[index].fieldType == 'number'
                      ) {
                        value = Number(value);
                        const prop = Number(item.properties[index]);
                        symbol = symbol === '=' ? '===' : symbol;
                        showItem = eval ( prop + symbol + value);
                      } else {
                        // string
                        showItem = isStringAttributeMatchingFilter(item, index, symbol, value);
                      }
                    } else {
                      showItem = isStringAttributeMatchingFilter(item, index, symbol, value);
                    }
                  } else {
                    showItem = false;
                  }
                }
              }
              return showItem;
            };
          },
          fti: function () {
            return function () {
              return true;
            };
          },
        };

        /**
         * Vérifie si la valeur d'un attribut string d'une ligne de datatable correspond
         * au filtre saisi sur cet attribut dans la barre des filtres du header de la datatable
         * @param item objet/ligne de la datatable
         * @param index rang de l'attribut dans les propriétés de l'attribut
         * @param symbol type de correspondance à vérifier: '=' (defaut) / 'equals' / 'startWith'
         * @param filter saisie dans l'input du filtre de l'attribut dans la barre des filtres
         * @return true si la ligne doit être affichée car la valeur de l'attribut
         * de la ligne correspond au filtre saisi
         */
        const isStringAttributeMatchingFilter = (item, index, symbol, filter) => {
          // cast as string or indexOf may throw an error

          let cmp = '' + item.properties[index];
          if ((!scope.sensible || !scope.sensible.case) && typeof filter === 'string') {
            cmp = cmp.toLocaleLowerCase();
            filter = filter.toLocaleLowerCase();
          }
          let propertyMatchFilter = cmp.includes(filter);
          if (scope.advancedFiltersConfiguration && scope.advancedFiltersConfiguration.text) {
            switch (symbol) {
              case 'equals':
                propertyMatchFilter = cmp === filter;
                break;
              case 'startswith':
                propertyMatchFilter = cmp.startsWith(filter);
                break;
            }
          }
          return !(!Object.prototype.hasOwnProperty.call(item.properties, index)
            || !propertyMatchFilter);
        };


        const getDateClause = (criteriaSymbol, attributeName, criteriaValue, hourMatters,
          debutJour, finJour) => {
          switch (criteriaSymbol) {
            case '=':
              return hourMatters
                ? ' AND ' + attributeName + ' = ' + criteriaValue
                : ' AND ' + attributeName + ' BETWEEN ' + debutJour + ' AND ' + finJour;
            case '>=':
              return hourMatters
                ? ' AND (' + attributeName + ' > ' + criteriaValue + ' OR ' +
                    attributeName + ' = ' + criteriaValue + ')'
                : ' AND (' + attributeName + ' > ' + debutJour + ' OR ' +
                    attributeName + ' = ' + debutJour + ')';
            case '<=':
              return hourMatters
                ? ' AND (' + attributeName + ' < ' + criteriaValue + ' OR ' +
                    attributeName + ' = ' + criteriaValue + ')'
                : ' AND (' + attributeName + ' < ' + finJour + ' OR ' +
                    attributeName + ' = ' + finJour + ')';
            case '>':
              return hourMatters
                ? ' AND ' + attributeName +' > ' + criteriaValue
                : ' AND ' + attributeName + ' > ' + finJour;
            case '<':
              return hourMatters
                ? ' AND ' + attributeName + ' < ' + criteriaValue
                : ' AND ' + attributeName + ' < ' + debutJour;
            default:
              return '';
          }
        };


        const builWhereClauseFromCriterias = (criterias) => {
          const keys = Object.keys(criterias);
          let  whereFilter = '',
            liaisonFilter = {};

          if (keys.length > 0) {
            for (var k in keys) {
              if (angular.isDefined(k)) {
                var criteriaValue = criterias[keys[k]].value,
                  criteriaSymbol = criterias[keys[k]].symbol;

                if (
                  angular.isDefined(criterias[keys[k]].valueExtra) &&
                angular.isDefined(criterias[keys[k]].value)
                ) {
                  var criteriaExtra = criterias[keys[k]].valueExtra;
                } else if (
                  (angular.isDefined(criterias[keys[k]].valueExtra) &&
                  !criteriaValue) ||
                criteriaValue == null
                ) {
                  criteriaValue = criterias[keys[k]].valueExtra;
                }

                if (
                  angular.isDefined(criterias[keys[k]].valueExtra) &&
                angular.isDefined(criterias[keys[k]].value)
                ) {
                  var criteriaSymbolExtra = criterias[keys[k]].symbolExtra;
                } else if (
                  (angular.isDefined(criterias[keys[k]].symbolExtra) &&
                  !criteriaSymbol) ||
                criteriaSymbol == null
                ) {
                  criteriaSymbol = criterias[keys[k]].symbolExtra;
                }

                if (
                  angular.isDefined(criteriaValue) &&
                criteriaValue !== null &&
                criteriaValue != ''
                ) {
                // @RB oct.2017 ca avait ete commenté pour une bonne raison
                // j'imagine ?
                // donc reactivé pour l'ANC seulement pour le moment, mais
                // sans doute necessaire partout car les filtres avec des '
                // ne marchent pas dans le cas classique
                  if (
                    angular.isDefined(extraWebService) &&
                  Object.prototype.toString.call(criteriaValue) ==
                    '[object String]'
                  ) {
                    criteriaValue = criteriaValue.replace(/'/g, "''");
                  }

                  if (
                    scope.displayAttributesType[keys[k]] &&
                  scope.displayAttributesType[keys[k]].filterType ==
                    'default'
                  ) {
                    switch (scope.displayAttributesType[keys[k]].fieldType) {
                      case 'string':
                        if (scope.specialCassandraFilter) {
                          whereFilter +=
                          ' AND "' +
                          keys[k] +
                          '" ' +
                          '=' +
                          " '" +
                          criteriaValue +
                          "'";
                        } else {
                          if (angular.isObject(criteriaValue)) {
                            if (keys[k] == 'prochain_controle') {
                              var mTextFilter = extractMTextFilterProchainControle(
                                criteriaValue
                              );
                            } else {
                              mTextFilter =
                              ' AND ' +
                              keys[k] +
                              " IN ('" +
                              Object.keys(criteriaValue)
                                .map(function (key) {
                                  return criteriaValue[key].key;
                                })
                                .join("','") +
                              "') ";
                            }
                          } else {
                            if (
                              scope.isAncBacListeDossiers &&
                            keys[k] == 'adresse_ville'
                            ) {
                              criteriaSymbol = 'equals';
                            }

                            mTextFilter = '';
                            if (keys[k] === 'avis_dernier_controle') {
                              if (
                                criteriaValue.indexOf(
                                  'Tous avis confondus'
                                ) === -1
                              ) {
                                mTextFilter =
                                ' AND ' +
                                keys[k] +
                                ' ' +
                                caseFilter +
                                " '" +
                                criteriaValue +
                                "'";
                              }
                            } else {
                              mTextFilter =
                              ' AND ' +
                              keys[k] +
                              ' ' +
                              caseFilter +
                              " '%" +
                              criteriaValue +
                              "%'";
                            }

                            // default

                            if (
                              angular.isDefined(criteriaExtra) &&
                            criteriaExtra != null
                            ) {
                              whereFilter +=
                              ' AND ' +
                              keys[k] +
                              ' ' +
                              caseFilter +
                              "  '%" +
                              criteriaExtra +
                              "%'";
                            }
                            // advanced
                            if (
                              scope.advancedFiltersConfiguration &&
                            scope.advancedFiltersConfiguration.text
                            ) {
                              if (criteriaSymbolExtra == 'contains') {
                                // default
                              }
                              if (criteriaSymbolExtra == 'startswith') {
                                whereFilter =
                                ' AND ' +
                                keys[k] +
                                ' ' +
                                caseFilter +
                                " '" +
                                criteriaExtra +
                                "%'";
                              }
                              if (criteriaSymbolExtra == 'equals') {
                                whereFilter =
                                ' AND ' +
                                keys[k] +
                                ' ' +
                                caseFilter +
                                " '" +
                                criteriaExtra +
                                "'";
                              }

                              if (criteriaSymbol == 'contains') {
                                // default
                              }
                              if (criteriaSymbol == 'startswith') {
                                mTextFilter =
                                ' AND ' +
                                keys[k] +
                                ' ' +
                                caseFilter +
                                " '" +
                                criteriaValue +
                                "%'";
                              }
                              if (criteriaSymbol == 'equals') {
                                mTextFilter =
                                ' AND ' +
                                keys[k] +
                                ' ' +
                                caseFilter +
                                " '" +
                                criteriaValue +
                                "'";
                              }
                            }
                          }

                          whereFilter += mTextFilter;
                        }
                        break;
                      case 'number':
                        if (scope.specialCassandraFilter) {
                          whereFilter +=
                          ' AND "' +
                          keys[k] +
                          '" ' +
                          criteriaSymbol +
                          ' ' +
                          criteriaValue;
                        } else {
                          whereFilter +=
                          ' AND ' +
                          keys[k] +
                          ' ' +
                          criteriaSymbol +
                          ' ' +
                          criteriaValue;
                        }
                        break;
                      case 'boolean':
                        if (scope.specialCassandraFilter) {
                          whereFilter +=
                          ' AND "' + keys[k] + '" = ' + criteriaValue;
                        } else {
                          whereFilter +=
                          ' AND ' + keys[k] + ' = ' + criteriaValue;
                        }
                        break;
                      case 'date':
                        // gestion des timestamp 0, ce qui arrive quand on
                        // efface manuellement une date du champ
                        try {
                          if (moment(new Date(criteriaValue)).unix() == 0) {
                            break;
                          }
                        } catch (err) {
                          break;
                        }

                        var debutJour = $filter('date')(
                            new Date(criteriaValue),
                            'yyyy-MM-ddTHH:mm:ss.sssZ'
                          ),
                          finJour = $filter('date')(
                            moment(new Date(criteriaValue))
                              .hours(23)
                              .minutes(59)
                              .toDate(),
                            'yyyy-MM-ddTHH:mm:ss.sssZ'
                          );

                        var hourMatters =
                        scope.filterHour &&
                        angular.isDefined(scope.criterias[keys[k]].hour);

                        if (criteriaSymbol != 'between') {
                          if (scope.specialCassandraFilter) {
                            whereFilter +=
                            ' AND "' +
                            keys[k] +
                            '"' +
                            criteriaSymbol +
                            " '" +
                            $filter('date')(
                              new Date(criteriaValue),
                              'yyyy-MM-dd'
                            ) +
                            "'";
                          } else {
                            whereFilter += getDateClause(criteriaSymbol, keys[k],
                              criteriaValue, hourMatters, debutJour, finJour);
                          }
                        } else {
                          // tant que deuxieme date pas remplie on ne
                          // considere que la premiere en mode >=
                          whereFilter = hourMatters
                            ? ' AND (' +
                            keys[k] +
                            ' AFTER ' +
                            criteriaValue +
                            ' OR ' +
                            keys[k] +
                            ' = ' +
                            criteriaValue +
                            ')'
                            : ' AND (' +
                            keys[k] +
                            ' AFTER ' +
                            debutJour +
                            ' OR ' +
                            keys[k] +
                            ' = ' +
                            debutJour +
                            ')';

                          var criteriaValueMax = criterias[keys[k]].valuemax;
                          if (
                            angular.isDefined(criteriaValueMax) &&
                          criteriaValueMax != '' &&
                          moment(new Date(criteriaValueMax)).unix() !== 0
                          ) {
                            var finJourMax = $filter('date')(
                              moment(new Date(criteriaValueMax))
                                .hours(23)
                                .minutes(59)
                                .toDate(),
                              'yyyy-MM-ddTHH:mm:ss.sssZ'
                            );

                            if (
                              scope.filterHour &&
                            angular.isDefined(
                              scope.criterias[keys[k]].hourmax
                            )
                            )
                              whereFilter +=
                              ' AND (' +
                              keys[k] +
                              ' BEFORE ' +
                              criteriaValueMax +
                              ' OR ' +
                              keys[k] +
                              ' = ' +
                              criteriaValueMax +
                              ')';
                            else
                              whereFilter +=
                              ' AND ' + keys[k] + ' BEFORE ' + finJourMax;
                          }
                        }
                        break;
                      default:
                        if (scope.specialCassandraFilter)
                          whereFilter +=
                          ' AND "' +
                          keys[k] +
                          '" ' +
                          '=' +
                          " '" +
                          criteriaValue +
                          "'";
                        else
                          whereFilter +=
                          ' AND ' +
                          keys[k] +
                          ' ' +
                          caseFilter +
                          " '%" +
                          criteriaValue +
                          "%'";
                        break;
                    }
                  } else {
                    if (angular.isUndefined(criteriaSymbol))
                      criteriaSymbol = '';

                    // string
                    if (criteriaSymbol == 'startswith') {
                      criteriaValue = 'startswith___' + criteriaValue;
                    } else if (criteriaSymbol == 'equals') {
                      criteriaValue = 'equals___' + criteriaValue;
                    } else {
                      criteriaValue = criteriaSymbol + '___' + criteriaValue;
                    }

                    liaisonFilter[keys[k]] = criteriaValue;
                  }
                }
              }

              // ssi vide et ne commence pas par 1=1
              if (whereFilter != '' && whereFilter.indexOf('1=1 ') == -1) {
                whereFilter = '1=1 ' + whereFilter;
              }
            }

            //handle fid where clause
            if (criterias.fid.symbol && criterias.fid.value) {
              if (whereFilter.length > 0) {
                whereFilter += ' AND ';
              }
              whereFilter += 'fid' + criterias.fid.symbol + criterias.fid.value + ' ';
            }
          }
          return {
            keysLength: keys.length,
            whereFilter: whereFilter,
            liaisonFilter: liaisonFilter
          };
        };


        /**
         * For fti sources only, Update the where clause when criterias are
         * changed
         */
        scope.whereFilter = '';
        // used for datatable showing complex objects from other tables (ex
        // ancapp>dossiers)
        scope.liaisonFilter = '';

        // temporisation
        var filterTextTimeout;

        scope.$watch(
          'criterias',
          function (criterias) {
            if (filterTextTimeout) $timeout.cancel(filterTextTimeout);

            if (scope.dataOrigin == 'geojson') {
              reloadTable();
              return;
            }

            const res = builWhereClauseFromCriterias(criterias);
            if (res.keysLength) {
              filterTextTimeout = $timeout(() => {
                scope.whereFilter = res.whereFilter;
                scope.liaisonFilter = JSON.stringify(res.liaisonFilter);
              }, 500);
            }
          },
          1
        );

        /**
         * createCsvFile
         *
         * @param toExport
         * @param data
         * @param restrictToSelectedLines
         *            n'exporte que les lignes selectionnees dans la datatable
         */
        const createCsvFile = (toExport, data, restrictToSelectedLines) => {
          restrictToSelectedLines = restrictToSelectedLines || false;

          data.forEach(function (dataRow) {
            // pas concerne
            if (
              restrictToSelectedLines &&
              Object.keys(scope.selectedLines).length &&
              !scope.selectedLines[dataRow.id]
            ) {
              return;
            }

            let rowsToAdd = getMaxLengthOfArrayChildAttributes(dataRow);
            for (let rowIndex = 0; rowIndex < rowsToAdd; rowIndex++) {
              const tmpObj = [];
              scope.displayAttributes.forEach(function (att) {
                const info = scope.displayAttributesType[att.name];
                let value = '';
                let valueAgent = '';
                let heureRdv = '';
                const attributeInfo = scope.displayAttributes.find(
                  (attribute) => attribute.name === att.name
                );
                const attributeHasCsvInfo = attributeInfo
                  && Object.prototype.hasOwnProperty.call(attributeInfo, 'csvValueInfo');
                if (
                  info.fieldType === 'ObjectsArray' ||
                  attributeHasCsvInfo ||
                  att.name == 'type_dernier_controle' ||
                  att.name == 'prochain_controle' ||
                  att.name == 'date_prochain_controle'
                ) {
                  if (attributeHasCsvInfo && dataRow.properties.proprietaires.length > rowIndex) {
                    value = attributeInfo.csvValueInfo
                      .map((propertyName) => {
                        const csvValue =
                            dataRow.properties[
                              attributeInfo.parentPropertyName
                            ][rowIndex][propertyName];
                        if (propertyName === 'typeProprietaire') {
                          const listeId =
                              $rootScope.xgos.sector === 'anc'
                                ? 'kis_anc_dossier_proprietaire'
                                : 'kis_bac_dossier_proprietaire';
                          const appListeDeroulante = FeatureTypeFactory.getFeatureByUid(
                            listeId
                          );
                          const typeProprietaireListeDeroulante = CommonFactory.getListeDeroulanteValues(
                            appListeDeroulante,
                            'type_proprietaire'
                          );
                          return gaJsUtils.getTypeDeProprietaireValue(
                            csvValue,
                            '\xa0',
                            typeProprietaireListeDeroulante
                          );
                        }
                        return csvValue || '';
                      })
                      .join('\xa0');
                  } else {
                    if (att.name == 'type_dernier_controle') {
                      value = dataRow.properties.type_dernier_controle
                        ? dataRow.properties.type_dernier_controle
                        : '';
                      valueAgent = dataRow.properties.dernierControle.agent
                        ? dataRow.properties.dernierControle.agent
                        : '';

                      value = CommonFactory.findControlTypeLabel(
                        value,
                        CurrentAppFactory.appCfg.main.controleCfg
                      );
                    } else if (att.name == 'prochain_controle') {
                      if ($rootScope.xgos.sector == 'bac') {
                        value = dataRow.properties.prochain_controle
                          ? dataRow.properties.prochain_controle
                          : '';
                        valueAgent = dataRow.properties.prochainControle.agent
                          ? dataRow.properties.prochainControle.agent
                          : '';
                      } else {
                        value = dataRow.properties
                          .type_prochain_controle_preconise
                          ? dataRow.properties.type_prochain_controle_preconise
                          : '';
                        valueAgent = dataRow.properties.agent_prochain_rdv
                          ? dataRow.properties.agent_prochain_rdv
                          : '';
                      }
                      value = CommonFactory.findControlTypeLabel(
                        value,
                        CurrentAppFactory.appCfg.main.controleCfg
                      );
                    } else if (att.name == 'date_prochain_controle') {
                      value = dataRow.properties.date_prochain_controle
                        ? dataRow.properties.date_prochain_controle
                        : '';
                      heureRdv = $filter('date')(value, 'HH:mm');
                      value = $filter('date')(value, 'dd/MM/yyyy');
                    } else {
                      value = dataRow && dataRow.properties && dataRow.properties[att.name] &&
                        dataRow.properties[att.name].length > rowIndex
                        ? dataRow.properties[att.name][rowIndex][info.key]
                        : '';
                    }
                  }
                } else {
                  value = scope.renderValue(dataRow, att.name, true);
                }
                value = scope.normalizeTextToCsv(value);

                tmpObj.push(value);
                if (
                  att.name == 'type_dernier_controle' ||
                  att.name == 'prochain_controle'
                ) {
                  valueAgent = scope.normalizeTextToCsv(valueAgent);
                  tmpObj.push(valueAgent);
                }
                if (att.name == 'date_prochain_controle') {
                  heureRdv = scope.normalizeTextToCsv(heureRdv);
                  tmpObj.push(heureRdv);
                }
              });
              toExport.push(tmpObj);
            }
          });
        };

        const getMaxLengthOfArrayChildAttributes = (dataRow)=>{
          let max = 1;
          scope.displayAttributes.forEach(function (att, index) {
            const info = scope.displayAttributesType[att.name];
            if (info.fieldType === 'ObjectsArray') {
              max = Math.max(max, dataRow.properties[att.name].length);
            }
          });
          return max;
        };

        /**
         * Assure que le contenu de l'attribut ne perturbe pas le formatage d'un fichier CSV.
         * Un retour à la ligne ou un espace peut perturber la délimitation des colonnes et
         * des lignes
         * @param {string|number} text valeur de l'attribut
         * @return {string} valeur de l'attribut normalisée
         */
        scope.normalizeTextToCsv = (text) => {
          if (text) {
            // Convertit le texte en chaîne de caractères, puis supprime tous les retours
            // à la ligne (\r\n, \n, \r)
            text = text.toString()
              .replace(/\r\n/g, '')
              .replace(/\n/g, '')
              .replace(/\r/g, '');

            if (text.indexOf(' ') > -1 ){

              // Si la chaîne contient au moins un espace, tous les espaces sont remplacés
              // par un espace insécable
              return text.replace(new RegExp(escapeRegExp(' '), 'g'), '\xa0');

            } else {
              // Si le texte ne contient pas d'espace, retourne le texte tel quel
              return text;
            }
          } else {
            // Si le texte est vide ou nul, retourne une chaîne vide
            return '';
          }
        };


        function escapeRegExp(string) {
          return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        }

        scope.onCsvDelimiterChange = function (columnDelimiter) {
          scope.columnDelimiter = columnDelimiter;
        };

        var exportcsvdialog;
        scope.showConfigSparator = function () {
          exportcsvdialog = ngDialog.openConfirm({
            template:
              'js/XG/widgets/utilities/data/views/cfg/csvFormatPrompt.html',
            className: 'ngdialog-theme-plain nopadding miniclose',
            scope: scope,
            showClose: true,
          });
          exportcsvdialog.then(function (data) {
            scope.configuser.columnDelimiter = data;
            if (data === 'TAB') {
              scope.configuser.columnDelimiter = '\t';
            }
            if (angular.isDefined(scope.saveConfig))
              scope.saveConfig(scope.configuser);
          });
        };

        /**
         * exportCsv if scope.exportCsv
         */
        if (angular.isUndefined(scope.configuser))
          scope.configuser = {
            columnDelimiter: ';',
          };

        /**
         * ExportCsvFile
         */
        scope.exportCsvFile = function () {
          if (!scope.tableParams.data.length) return;

          scope.exportPending = true;
          var toExport = [];
          var headers = [];
          // push headers
          scope.displayAttributes.forEach(function (att) {
            let columnHeader = scope.renderTitleValue(att, true);
            columnHeader = scope.normalizeTextToCsv(columnHeader);
            headers.push(columnHeader);
            if(att.name == 'type_dernier_controle' || att.name == 'prochain_controle'){
              headers.push('Agent');
            }
            if(att.name == 'date_prochain_controle'){
              headers.push('Heure de rendez-vous');
            }
          });
          toExport.push(headers);
          gaDomUtils.showGlobalLoader();

          // export tout pas seulement les values qui s'affichent (contredit par KIS-3107?)
          // cas du geojson
          if (scope.geojsondata !== false) {


            // KIS-3107:
            const isDataFiltered = Object.values(scope.criterias).some(
              criteria => typeof criteria.value === 'string' && criteria.value.length > 0);
            const features = isDataFiltered ? scope.tableParams.data : scope.geojsondata.features;

            createCsvFile(toExport, features, true);

            gaDomUtils.hideGlobalLoader();
            scope.exportPending = false;
            return toExport;
          }
          // extra web service par ex anc
          else if (extraWebService && typeof scope.datawebservice === 'function') {
            var tmpParameters = angular.copy(extraWebServiceParameters);
            tmpParameters.params.count = scope.totalElementNumber;
            tmpParameters.params.page = 1;

            tmpParameters.params.isExport = true;
            // si selection en cours on se restreint à ces ids seulement, dès la
            // requête
            if (Object.keys(scope.selectedLines).length) {
              tmpParameters.params.force_restricted_ids = Object.keys(
                scope.selectedLines
              ).join(',');
            }
            gaDomUtils.hideGlobalLoader();
            require('toastr').info("L'opération d'export a été lancée");

            return scope.datawebservice(tmpParameters).then(
              function (res) {
                createCsvFile(toExport, res.data.features, true);
                scope.exportPending = false;
                return toExport;
              },
              function () {
                scope.exportPending = false;
              }
            );
            // export dans un thread (utilisé pour les exports CSV depuis admin/feature)
          }else if(scope.exportCsvInThread){
            let deferred = $q.defer();
            QueryFactory.dataByThread(
              webServiceParameters.uid,
              webServiceParameters.filter,
              webServiceParameters.crs,
              1, // si on demande 999999 elements par page, on ne doit demander que la première page
              999999,
              webServiceParameters.sort
            ).then(
              (res) => {
                if (res.data.etat !== 'RUNNING' && res.data.etat !== 'FINISHED') {
                  gaDomUtils.hideGlobalLoader();
                  scope.exportPending = false;
                }else{
                  scope.checkProcessInProgress(res.data, undefined, toExport, deferred);
                }
              },
              () => {
                scope.exportPending = false;
                gaDomUtils.hideGlobalLoader();
              }
            );
            return deferred.promise;
            // classique
          } else {
            return QueryFactory.data(
              webServiceParameters.uid,
              webServiceParameters.filter,
              webServiceParameters.crs,
              webServiceParameters.page,
              999999,
              webServiceParameters.sort
            ).then(
              (res) => {
                createCsvFile(toExport, res.data.features, true);
                gaDomUtils.hideGlobalLoader();
                scope.exportPending = false;
                return toExport;
              },
              () => {
                scope.exportPending = false;
                gaDomUtils.hideGlobalLoader();
              }
            );
          }
        };

        let stop;
        scope.checkProcessInProgress = (resdata0, intervalMsec, toExport, deferred) => {
          if (intervalMsec === undefined){
            intervalMsec = 750;
          }
          scope.intervalCounter = 0;
          stop = $interval(function () {
            QueryFactory.getDataProgression(resdata0).then(
              (res) => {
                if (res.data.etat === 'FINISHED') {

                  // stoppe la répétition de l'appel api
                  $interval.cancel(stop);
                  if (res.data.extraInfo && res.data.extraInfo.length > 0){

                    // parse la propriété "extraInfo" du processus renvoyé par le back
                    const geojson = JSON.parse(res.data.extraInfo);
                    const features = geojson.features;

                    // créé le fichier csv en ajoutant les features dans variable
                    // "toExport" contenant déjà les en-tetes
                    createCsvFile(toExport, features, true);

                    // renvoie la promesse une après l'exécution de createCsvFile
                    deferred.resolve(toExport);

                    scope.exportPending = false;
                    gaDomUtils.hideGlobalLoader();
                  }
                } else if (scope.loadingLaunched && res.data.etat === 'FAILED') {
                  getDataFailed(res, stop);
                } else {
                  //-- Toujours en train de tourner
                  if (scope.intervalCounter > 100 && intervalMsec < 90000) {
                    //-- Au plus c'est long, au moins on va interroger
                    //-- le serveur fréquemment sur l'état d'avancement.
                    $interval.cancel(stop);
                    scope.checkProcessInProgress(resdata0, 2 * intervalMsec, toExport);
                    scope.intervalCounter = 0;
                  }
                }
              },
              () => {
                if (scope.loadingLaunched) {
                  gaDomUtils.removeLocalLoader('.' + scope.widgetName);
                  $interval.cancel(stop);
                  require('toastr').error(
                    $filter('translate')('tools.majicedigeo.edigeo.loadfailed')
                  );
                  scope.conversionIsRunning = false;
                }
              }
            );
          }, intervalMsec);
        };


        /**
         * Retourne la liste des restrictions de type domaine éventuelles
         *
         * @param {obj}
         *            att [Définition du fti]
         * @return [string] [valeur de la clé du domaine]
         */
        scope.getDomaineRestrictions = function (att) {
          // domain
          var domainRestriction = false;
          att.restrictions.forEach(function (attr) {
            if (attr.type == 'Domain' && !domainRestriction) {
              domainRestriction = attr;
            }
          });
          return domainRestriction;
        };

        /**
         * Set column filter value programmatically setColFilterValue
         */
        scope.setColFilterValue = function (attributeName, value) {
          if (attributeName && value != undefined) {
            if (!angular.isDefined(scope.criterias[attributeName])) {
              scope.criterias[attributeName] = {};
            }
            scope.criterias[attributeName].value = value.toString();
          } else {
            console.error('Wrong parameters: setColFilterValue(' + attributeName + ', ' + value + ')');
          }
        };
        /**
         * Rendu de la valeur d'un domaine sur la datatable
         *
         * @param {obj}
         *            att [Définition du fti]
         * @param {obj}
         *            prop [Définition de notre objet // clé de la propriété]
         * @return [string] [valeur de la clé du domaine]
         */
        scope.renderRestrict = function (att, obj) {
          // console.log(scope.fti)
          var v = obj.properties[att.name];

          for (var i = att.restrictions.length - 1; i >= 0; i--) {
            if (v != undefined)
              if (att.restrictions[i].type == attributeRestrictionsUtils.DOMAIN) {
                // RÃ©cupÃ¨re l'objet restriction de type Domain
                // Test si plusieurs domaines afin d'Ãªtre sur qu'il renvoie
                // quelquechose
                if (att.restrictions[i].listofValues[v] !== undefined) {
                  var translated = $filter('translate')(
                    'features.' + scope.fti.name + '.domaine.' + v
                  );
                  // Renvoi la valeur de la clé recherché
                  return $filter('translate')(
                    'features.' +
                      scope.fti.name +
                      '.domaine.' +
                      att.name +
                      '.' +
                      v
                  ).indexOf('features.') === -1
                    ? $filter('translate')(
                      'features.' +
                          scope.fti.name +
                          '.domaine.' +
                          att.name +
                          '.' +
                          v
                    )
                    : att.restrictions[i].listofValues[v];
                } // eg.'features.Interlocuteur.domaine.testValue'
              }
          }
          return v;
        };

        /**
         * actionFromDatatable Permet de générer certaines actions depuis le
         * clic sur un bouton dans une colonne de la datatable
         */
        scope.actionFromDatatable = function (params) {
          if (!angular.isDefined(params.fn)) {
            console.log(
              '%c aucune fonction passée',
              'background: #f00; color: #fff'
            );
            return;
          }

          switch (params.fn) {
            case 'openCalendarFromControleAnc':
            // recuperation de l'evenement associé au dossier
            // geteventidbycontrole
              if (angular.isDefined(params.data.controleId)) {
                gaDomUtils.showGlobalLoader();
                CalendarFactory.getEventByControl(params.data.controleId).then(
                  function (res) {
                    gaDomUtils.hideGlobalLoader();
                    if (res.totalFeatures == 0) {
                      require('toastr').error(
                        "L'événement n'a pas pu être récupéré"
                      );
                      return;
                    } else {
                      scope.toggleAllFromSelection(false);
                      scope.toggleFromSelection(
                        { id: params.data.dossierId },
                        true
                      );
                      scope.allLinesSelected = false;
                      $timeout(() => {
                        $rootScope.$emit('openEventFromKis', {
                          feature: res.data.features[0],
                          controleId: params.data.controleId,
                          dossierId: params.data.dossierId,
                        });
                      });
                    }
                  },
                  function () {
                    require('toastr').error(
                      "L'événement n'a pas pu être récupéré"
                    );
                    gaDomUtils.hideGlobalLoader();
                  }
                );
              }

              break;
            case 'edit_alerte_dossier':
              if (angular.isDefined(params.data.report)) {
                console.log(params.data.report);
                $rootScope.$broadcast(
                  'edit_alerte_dossier',
                  params.data.report
                );
              }
              break;
            case 'openAncControle':
              $rootScope.$broadcast('anc_open_controle', [
                {
                  dossier_id: params.data.dossierId,
                  controle_id: params.data.controleId,
                },
              ]);
              break;
            case 'openBacControle':
              $rootScope.$broadcast('bac_open_controle', [
                {
                  dossier_id: params.data.dossierId,
                  controle_id: params.data.controleId,
                },
              ]);
              break;
            case 'updateDatePreconiseeAnc':
              gaDomUtils.showGlobalLoader();
              var date_prochain_controle_preconise = params.data.obj.properties.date_prochain_controle_preconise;
              var formated_date = null;
              if (gaJsUtils.notNullAndDefined(date_prochain_controle_preconise)) {
              // Si définie, formater la date
                formated_date = $filter('date')(date_prochain_controle_preconise, 'yyyy-MM-ddTHH:mm:ss.sssZ');
              }
              EditFactory.updatebyid(
                'kis_anc_dossier',
                params.data.obj.id,
                'date_prochain_controle_preconise',
                'date',
                formated_date
              ).then(
                function () {
                  require('toastr').success('Date préconisée mise à jour');
                  gaDomUtils.hideGlobalLoader();
                },
                function () {
                  require('toastr').error(
                    'Erreur lors de la mise à jour de la date préconisée'
                  );
                  gaDomUtils.hideGlobalLoader();
                }
              );
              break;
            case 'updateDatePreconiseeBac':
              gaDomUtils.showGlobalLoader();
              EditFactory.updatebyid(
                'kis_bac_dossier',
                params.data.obj.id,
                'date_prochain_controle',
                'date',
                $filter('date')(
                  params.data.obj.properties.date_prochain_controle,
                  'yyyy-MM-ddTHH:mm:ss.sssZ'
                )
              ).then(
                function () {
                  require('toastr').success('Date préconisée mise à jour');
                  gaDomUtils.hideGlobalLoader();
                },
                function () {
                  require('toastr').error(
                    'Erreur lors de la mise à jour de la date préconisée'
                  );
                  gaDomUtils.hideGlobalLoader();
                }
              );
              break;
            case 'updateDateRaccordementAnc':
              gaDomUtils.showGlobalLoader();
              EditFactory.updatebyid(
                'kis_anc_dossier',
                params.data.obj.id,
                'date_raccordement',
                'date',
                $filter('date')(
                  params.data.obj.properties.date_raccordement,
                  'yyyy-MM-ddTHH:mm:ss.sssZ'
                )
              ).then(
                function () {
                  require('toastr').success('Date de raccordement mise à jour');
                  gaDomUtils.hideGlobalLoader();
                },
                function () {
                  require('toastr').error(
                    'Erreur lors de la mise à jour de la date de raccordement'
                  );
                  gaDomUtils.hideGlobalLoader();
                }
              );
              break;
          }
        };
        var _lang = localStorage.getItem('current_language');
        if (!_lang) _lang = 'fr';
        scope.openhyperlink = function (obj, attName) {
          var link = obj.properties[attName];
          if (angular.isDefined(link)) {
            window.open(link);
          }
        };
        /**
         * Function used to render the column value doesnt do anything if no
         * renduColonnes is passed to the datatable
         *
         * @param v
         * @param attName
         * @returns {*}
         */
        scope.renderValue = function (obj, attName, notHtml) {

          let cellValue = obj.properties[attName];

          const attributProp = scope.fti && Array.isArray(scope.fti.attributes)?
            scope.fti.attributes.find(x=> x.name === attName) : undefined;

          if (scope.translateAttrs
            && Object.prototype.hasOwnProperty.call(scope.translateAttrs, attName))
            return scope.translateAttrs[attName](cellValue);

          if (angular.isDefined(scope.renduColonnes)) {
            if (angular.isDefined(scope.renduColonnes[attName])) {
              return $sce.valueOf(scope.renduColonnes[attName](cellValue, obj, notHtml));
            }
          }

          if (
            scope.displayAttributesType[attName] &&
            scope.displayAttributesType[attName].fieldType == 'date' &&
            cellValue != null
          ) {
            cellValue = gaBrowserSniffer.parseDateSpecIE(cellValue);
            if (!angular.isDefined(scope.dateFormat)) {
              cellValue = cellValue.toLocaleDateString();
            } else {
              if (isNaN(cellValue.getTime())) {
                cellValue = $filter('date')(obj.properties[attName], scope.dateFormat);
              } else {
                cellValue = $filter('date')(cellValue, scope.dateFormat);
              }
            }
          }

          if (
            scope.displayAttributesType[attName] &&
            (scope.displayAttributesType[attName].fieldType == 'number'
              && attributProp && attributProp.interpretAsBoolean)
          ) {
            if (notHtml) {
              if (cellValue === true || cellValue === 1) {
                var trInput = $filter('translate')('common.export.true');
              } else {
                trInput = $filter('translate')('common.export.false');
              }
            } else {
              if (cellValue === true || cellValue === 1) {
                trInput =
                  '<div class="text-success bg-success text-center" style="width:20px;">\u2713</div>';
              } else {
                trInput =
                  '<div class="text-center" style="width:20px;">\u2718</div>';
              }
            }
            return trInput;
          }

          if (
            scope.displayAttributesType[attName] &&
            scope.displayAttributesType[attName].fieldType == 'boolean' &&
            cellValue != null
          ) {
            if (notHtml) {
              if (cellValue === true || cellValue === '1.0') {
                trInput = $filter('translate')('common.export.true');
              } else {
                trInput = $filter('translate')('common.export.false');
              }
            } else {
              if (cellValue === true || cellValue === '1.0') {
                trInput =
                  '<div class="text-success bg-success text-center" style="width:20px;">\u2713</div>';
              } else {
                trInput =
                  '<div class="text-center" style="width:20px;">\u2718</div>';
              }
            }
            return $sce.trustAsHtml(trInput);
          }

          if (
            scope.displayAttributesType[attName] &&
            (scope.displayAttributesType[attName].type == 'java.lang.Double' ||
              scope.displayAttributesType[attName].type == 'java.lang.Float') &&
            angular.isDefined(cellValue) &&
            cellValue !== null
          ) {
            if (
              angular.isDefined(scope.doubleDecimalFromStringSize) &&
              scope.doubleDecimalFromStringSize &&
              angular.isDefined(cellValue)
            ) {
              try {
                if (cellValue.toString().split('.').length > 1) {
                  if (cellValue.toString().split('.')[1].length > 5) {
                    if (_lang == 'fr')
                      cellValue = $filter('number')(Number.parseFloat(cellValue).toFixed(5));
                    else {
                      cellValue = Number.parseFloat(cellValue).toFixed(5);
                    }
                  } else {
                    if (_lang == 'fr')
                      cellValue = $filter('number')(
                        Number.parseFloat(cellValue).toFixed(
                          cellValue.toString().split('.')[1].length
                        )
                      );
                    else {
                      cellValue = Number.parseFloat(cellValue).toFixed(
                        cellValue.toString().split('.')[1].length
                      );
                    }
                  }
                }
              } catch (e) {
                console.error(e.stack);
                if (_lang == 'fr')
                  cellValue = $filter('number')(Number.parseFloat(cellValue).toFixed(2));
                else {
                  cellValue = Number.parseFloat(cellValue).toFixed(2);
                }
              }
            } else {
              if (_lang == 'fr') {
                let percision = 2;
                //get the correct precision to avoid trailing zeros
                for ( let attribute of scope.fti.attributes) {
                  if ((attribute.name == attName) && (attribute.size != undefined)) {
                    // for legacy reasons, a lot of feature have 500. it's too much.
                    percision = (attribute.size > 10) ? 2 : attribute.size;
                  }
                }
                // default precision of this filter is 3
                // $filter('number') respecte la locale courante
                // (dont le séparateur de décimales de la locale)
                cellValue = $filter('number')(Number.parseFloat(cellValue), percision);
              } else {
                cellValue = Number.parseFloat(cellValue);
              }
              // KIS-2920 Le point est le séparateur décimal à respecter en base données
              // quelque soit la locale, un double doit être affiché
              //avec "." comme séparateur de décimales.
              cellValue = cellValue.replace(',','.');
            }
          }

          try {
            if (
              cellValue &&
              scope.displayAttributesType[attName] &&
              scope.displayAttributesType[attName].type ===
                'java.lang.String' &&
              cellValue.indexOf('%20') !== -1
            ) {
              cellValue = gaUrlUtils.tryDecodeURIComponent(cellValue);
            }
          } catch (error) {}

          // if undefined, return empty string to avoid ghost values in the table
          // see ticket kis-1743
          return angular.isDefined(cellValue) ? cellValue : '';
        };

        scope.renderTooltip = (text) => {
          if (!scope.isAncBacListeDossiers) {
            return;
          }
          text = text || '';
          if (typeof text !== 'string') { text = JSON.stringify(text); }
          return text.replace(/<[^>]+>/gm, '');
        };


        /**
         * renderTitleValue [renderTitleValue description]
         *
         * @param {[type]}
         *            attName [description]
         * @return {[type]} [description]
         */
        scope.renderTitleValue = function (att, notHtml) {
          if (angular.isDefined(scope.renduTitleColonne)) {
            // on force l'alias contenu dans le fti, utile par ex dans le cas du
            // requetage ou il est possible de forcer
            // le nom d'une colonne, on ne veut pas qu'il soit traduit a nouveau
            if (scope.renduTitleColonne === false) return att.alias;

            if (angular.isDefined(scope.renduTitleColonne[att.name])) {
              return scope.renduTitleColonne[att.name](att.name, notHtml);
            } else {
              return $filter('translate')(
                'features.' + scope.fti.name + '.properties.' + att.name
              ).indexOf('features.') === -1
                ? $filter('translate')(
                  'features.' + scope.fti.name + '.properties.' + att.name
                )
                : att.alias
                  ? att.alias
                  : att.name;
            }
          } else {
            return $filter('translate')(
              'features.' + scope.fti.name + '.properties.' + att.name
            ).indexOf('features.') === -1
              ? $filter('translate')(
                'features.' + scope.fti.name + '.properties.' + att.name
              )
              : att.alias
                ? att.alias
                : att.name;
          }
        };

        $rootScope.$on('refreshDatatable', function (event, args) {
          if (args && args.uid == scope.fti.uid) {
            // trigger refresh, kinda hacky
            var tmp = angular.copy(scope.fti);
            scope.fti = tmp;
          }
          if (scope.keepSelectionOnPaginate) {
            scope.res = [];
            scope.selectedLines = {};
          }
        });

        if (angular.isDefined(scope.buttonEdit))
          scope.buttonactions = scope.buttonEdit;

        // toggle feature with specified uid
        $rootScope.$on('toggleDatatableFeature', function (event, args) {
          if (args && args.uid == scope.fti.uid) {
            scope.toggleFromSelection({ id: args.id });
          }
        });

        var caseFilter = 'ILIKE';
        scope.sensible = {};
        scope.sensible.case = false;
        scope.sensible_case_change = function () {
          caseFilter = 'ILIKE';
          if (scope.sensible.case == true) {
            caseFilter = 'LIKE';
          }
          scope.sensitive = scope.sensible.case;
          reloadTable();
        };
        function setMultiSelectConfiguration(rf, attName, groups, groupBy) {
          scope.multiSelects[attName] = true;
          if (scope.criterias[attName] == undefined)
            scope.criterias[attName] = { symbol: '=' };
          scope.criterias[attName].value = [];
          if (angular.isDefined(rf.groupBy)) {
            scope.criterias[attName].selectByGroupSettings = {
              scrollableHeight: '300px',
              scrollable: true,
              enableSearch: true,
              idProp: 'key',
              externalIdProp: 'key',
              selectByGroups: groups,
              groupByTextProvider: function (groupValue) {
                return groupValue;
              },
              groupBy: groupBy,
              buttonClasses: 'multiSelectFilterBtn',
            };
          } else
            scope.criterias[attName].selectSettings = {
              scrollableHeight: '300px',
              scrollable: true,
              enableSearch: true,
              buttonClasses: 'multiSelectFilterBtn',
            };

          scope.translationTexts = {
            checkAll: $filter('translate')('common.selectall'),
            uncheckAll: $filter('translate')('common.deselectall'),
            selectionCount: $filter('translate')('common.selectioncount'),
            selectionOf: '/',
            searchPlaceholder: $filter('translate')('common.toFilter'),
            buttonDefaultText: $filter('translate')('common.choisir'),
            dynamicButtonTextSuffix: $filter('translate')(
              'common.selectioncount'
            ),
          };
        }

        function listeItemMatchDependency(value, attName, mainDepField) {
          //-- Pas de valeur sélectionnée dans la liste principale
          //-- donc toutes les valeurs de la liste secondaire sont valides.
          if (scope.criterias[attName].value.length == 0) return true;

          //-- Des éléments de la liste principale sont sélectionnés,
          //-- il faut donc restreindre laliste secondaire selon la configuration,
          //-- en vérifiant la correspondance de la valeur de la liste secondaire
          //-- avec un élément de la liste principale.
          for (
            let ind = 0;
            ind < scope.criterias[attName].value.length;
            ind++
          ) {
            if (scope.criterias[attName].value[ind][mainDepField] == value)
              return true;
          }

          //-- La valeur ne correspond à aucun élément de la liste principale.
          return false;
        }

        function listItemMustBeShownAnyWay(item, cfg) {
          let show;
          if (cfg.showAnyWay) {
            for (var ind = 0; ind < cfg.showAnyWay.length; ind++) {
              show = cfg.showAnyWay[ind];
              if (item[show.field] == show.value) return true;
            }
          }
          return false;
        }

        function buildMultiSelectList(rf, attName) {
          let groups = false;
          let groupBy = false;
          let id = 1,
            attNameDep,
            mainDepField,
            secondDepField;

          if (rf.cfg.dependency) {
            attNameDep = rf.cfg.dependency.field;
            mainDepField = rf.cfg.dependency.thisDepField;
            secondDepField = rf.cfg.dependency.otherDepField;
            var rfDep = scope.renduFilters[attNameDep];
            var liste = angular.isArray(rfDep) ? rfDep : rfDep.liste;
            //-- Gardons la liste originale.
            liste = angular.copy(liste);
          }

          if (angular.isDefined(rfDep.groupBy)) {
            groupBy = rfDep.groupBy;
            groups = [];
            for (let ind = liste.length - 1; ind >= 0; ind--) {
              if (
                !listItemMustBeShownAnyWay(liste[ind], rf.cfg.dependency) &&
                !listeItemMatchDependency(
                  liste[ind][secondDepField],
                  attName,
                  mainDepField
                )
              )
                liste.splice(ind, 1);
            }
            liste.map(function (x) {
              if (groups.indexOf(x[rfDep.groupBy]) == -1)
                groups.push(x[rfDep.groupBy]);
              x.id = id++;
              if (attNameDep == 'avis_dernier_controle')
                x.key = x.label + '_TCONTROLE_' + x.type_controle;
              else {
                if (attName != 'prochain_controle') {
                  x.key = x.label;
                }
              }
            });
          } else {
            liste.map(function (x) {
              x.id = id++;
            });
          }

          if (rfDep.cfg) {
            if (rfDep.cfg.multiSelect)
              setMultiSelectConfiguration(rfDep, attNameDep, groups, groupBy);
          }

          scope.select_filter_values[attNameDep] = {
            liste: liste,
            groups: groups,
            groupBy: groupBy,
          };
        }

        /**
         *   Seul le cas de dépendance de filtre inter colonne de type
         * multiselect est géré à ce jour.
         */
        function setDependency(rf, attName) {
          if (rf.cfg.multiSelect) {
            //-- Un timeout est nécessaire pour attendre que le ng-model soit actualisé.
            //-- Le ng-model est la liste des éléments sélectionnée dans la liste principale.
            scope.criterias[attName].selectEvents = {
              onItemSelect: function () {
                $timeout(function () {
                  buildMultiSelectList(rf, attName);
                }, 100);
              },
              onItemDeselect: function () {
                $timeout(function () {
                  buildMultiSelectList(rf, attName);
                }, 100);
              },
              onSelectAll: function () {
                $timeout(function () {
                  buildMultiSelectList(rf, attName);
                }, 100);
              },
              onDeselectAll: function () {
                $timeout(function () {
                  buildMultiSelectList(rf, attName);
                }, 100);
              },
            };
          }
        }

        /**
         * Function used to render the column filter doesnt do anything if no
         * renduFilters is passed to the datatable
         *
         * @param v
         * @param attName
         * @returns {*}
         */
        scope.multiSelects = {};
        scope.select_filter_values = {};
        scope.renderFilterValue = function (v, attName) {
          // impossible filter sur le champ id
          if (attName != null && attName.toLowerCase() == 'id')
            return 'attribute_name_equals_id';

          if (angular.isDefined(scope.renduFilters)) {
            if (angular.isDefined(scope.renduFilters[attName])) {
              var rf = scope.renduFilters[attName];
              var liste = angular.isArray(rf) ? rf : rf.liste;
              var groups = false;
              var groupBy = false;
              let id = 1;
              if (angular.isDefined(rf.groupBy)) {
                groupBy = rf.groupBy;
                groups = [];
                liste.map(function (x) {
                  if (groups.indexOf(x[rf.groupBy]) == -1)
                    groups.push(x[rf.groupBy]);
                  x.id = id++;
                  if (attName == 'avis_dernier_controle')
                    x.key = x.label + '_TCONTROLE_' + x.type_controle;
                  else {
                    if (attName != 'prochain_controle') {
                      x.key = x.label;
                    }
                  }
                });
              } else {
                liste.map(function (x) {
                  x.id = id++;
                });
              }

              if (rf.cfg) {
                if (rf.cfg.multiSelect)
                  setMultiSelectConfiguration(rf, attName, groups, groupBy);
                if (rf.cfg.dependency) setDependency(rf, attName);
              }

              if (attName === 'avis_dernier_controle') {
                liste = liste.filter(
                  (el) => el && el.label !== 'Tous avis confondus'
                );
              }

              scope.select_filter_values[attName] = {
                liste: liste,
                groups: groups,
                groupBy: groupBy,
              };
              v = 'filter_select';
            }
          }

          return v;
        };

        scope.select_filter_values_extra = {};
        scope.renderExtraFilter = function (att) {
          if (
            scope.extraFilters &&
            angular.isDefined(scope.extraFilters[att.name])
          ) {
            if (scope.extraFilters[att.name].type == 'string_search') {
              scope.select_filter_values_extra[att.name] = {
                type: scope.extraFilters[att.name].type,
                symbol: scope.extraFilters[att.name].symbol,
                key: att.name,
              };
            } else {
              var rf = scope.extraFilters[att.name];
              var liste = angular.isArray(rf) ? rf : rf.liste;

              scope.select_filter_values_extra[att.name] = {
                liste: liste,
                key: rf.key,
              };
            }
            return true;
          }
          return false;
        };

        /**
         * renderFilterGrouping rendu des group by dans les filtres
         *
         * @param attname
         * @param group
         * @returns {Function}
         */
        scope.renderFilterGrouping = function (attname, group) {
          scope.selectGroupe = group;
          return function (item) {
            return item[scope.select_filter_values[attname].groupBy] == group;
          };
        };

        let currSortData = {};
        scope.sort_title_style = {};

        scope.sortTable = function (att) {
          if (!scope.data || !scope.data.length) {
            return false;
          }

          let sortOrder = 'asc';
          if (currSortData.column === att.name) {
            if (currSortData.order === 'asc') sortOrder = 'desc';
            if (currSortData.order === 'desc') sortOrder = '';
          }

          currSortData.order = sortOrder;
          currSortData.column = att.name;

          scope.sort_by_title =
            sortOrder === ''
              ? ''
              : currSortData.column + ',' + currSortData.order;

          scope.sort_title_style = currSortData;

          // reload the table
          if (scope.geojsondata === false) {
            scope.fti = angular.copy(scope.fti);
          } else {
            scope.geojsondata = angular.copy(scope.geojsondata);
          }
        };

        var reader = new FileReader(),
          blob,
          file;
        var Papa = require('papaparse');
        var liaisonsModal;
        scope.csvSources = [];
        scope.importObject = {};
        /**
         * loadCsvFile
         *
         * @param input
         */
        scope.loadCsvFile = function (input) {
          file = input.files[0];
          // csv only
          if (!~file.name.indexOf('.csv')) {
            require('toastr').error(
              $filter('translate')('csvgeocoder.csv_only')
            );
            return;
          }
          if (file.size >= 5000000) {
            require('toastr').error(
              $filter('translate')('csvgeocoder.file_too_heavy')
            );
            return;
          }

          reader.readAsText(file, 'utf-8');

          reader.onload = function () {
            try {
              scope.$apply(function () {
                blob = new Blob([reader.result], {
                  type: 'text/csv; charset=utf-8',
                });
                scope.currentFileName = file.name;

                scope.csvData = Papa.parse(reader.result, { header: true });

                console.log(scope.csvData.data);
                scope.csvSources = Object.keys(scope.csvData.data[0]);
                scope.importObject.ftid = scope.fti.uid;
                scope.importObject.liaisons = {};

                liaisonsModal = ngDialog.open({
                  template:
                    'js/XG/widgets/utilities/data/views/modals/modal.csv.import.html',
                  className:
                    'ngdialog-theme-plain width800 nopadding miniclose',
                  closeByDocument: false,
                  scope: scope,
                });
              });
            } catch (err) {}
          };
        };

        /**
         * add data in database
         */
        scope.submitCsvDataLiaison = function () {
          $timeout(function () {
            var features = {};
            features.type = 'FeatureCollection';
            features.features = [];
            var srid = '';
            if (angular.isDefined(scope.map)) {
              srid = scope.map.getView().getProjection().getCode();
            }

            var keys = Object.keys(scope.importObject.liaisons);
            var data = scope.csvData.data;

            for (var i = 0; i < data.length; i++) {
              var feature = {
                properties: {},
              };
              for (var j = 0; j < keys.length; j++) {
                for (var z = 0; z < scope.fti.attributes.length; z++) {
                  if (
                    scope.importObject.liaisons[keys[j]] ==
                    scope.fti.attributes[z].name
                  ) {
                    feature.properties[
                      scope.importObject.liaisons[keys[j]]
                    ] = gaJsUtils.castAttributeValueType(
                      data[i][keys[j]],
                      scope.fti.attributes[z].type,
                      'java.lang.String'
                    );
                  }
                }
              }
              feature.type = 'feature';
              if (scope.fti.geographic == true) {
                feature.geometry = {};
              }
              features.features.push(feature);
            }
            gaDomUtils.showGlobalLoader();
            EditFactory.add(scope.fti.uid, features, srid).then(
              function (res) {
                reloadTable();
                console.log(res.data);
                gaDomUtils.hideGlobalLoader();
              },
              function () {
                gaDomUtils.hideGlobalLoader();
              }
            );

            liaisonsModal.close();
          });
        };

        scope.translatealias = function (fti) {
          var alias = '';
          if (fti.name)
            alias =
              $filter('translate')('features.' + fti.name + '.alias').indexOf(
                'features.'
              ) === -1
                ? $filter('translate')('features.' + fti.name + '.alias')
                : fti.alias;
          return alias;
        };

        scope.translatealiasTemplate = function (fti) {
          var alias = '';
          if (fti.name)
            alias =
              $filter('translate')('features.' + fti.name + '.alias').indexOf(
                'features.'
              ) === -1
                ? $filter('translate')('features.' + fti.name + '.alias')
                : fti.alias;
          scope.alias = alias;
          return scope.alias;
        };

        scope.$on('emptyDatatableResult', function () {
          scope.res = [];
          scope.selectedLines = {};
          reloadTable();
        });
        scope.$on('addselectedLine', function (evt, obj) {
          scope.selectedLines[obj.id] = true;
          setResultat();
        });

        scope.$on('reloadDatatable', function (evt, arg, arg2) {
          if (arg && arg === scope.fti.uid) {
            if (arg2) {
              if (scope.askedAttributes)
                reloadAttributes(scope.askedAttributes);
              else reloadAttributes(scope.attributes);
              getAttributeTypes(1);
            }
            reloadTable();
          } else if (!arg) reloadTable();
        });

        scope.$on('updateobjectsHPoindatabase', function (evt, args) {
          if (args && args.res) {
            var objects = {
              type: 'FeatureCollection',
              features: [],
            };
            var obj = getElementHpoFromTable(args.res);
            if (obj) {
              obj.properties.CONFIGURER = true;
              obj.properties.DATE_CONFIG = HpoCarteAppFactory.getAppFactory().formatDate(
                new Date()
              );
              removeFromSelection(obj);
              delete scope.selectedLines[obj.id];
              objects.features.push(obj);
            }
            if (objects.features.length > 0) {
              EditFactory.update(
                args.fti.uid,
                objects,
                HpoCarteAppFactory.getAppFactory().getSrid()
              ).then(
                function () {
                  console.log(
                    $filter('translate')('hpo.data.exchange.succes'),
                    $filter('translate')('hpo.data.exchange.updates'),
                    false
                  );
                  reloadTable();
                },
                function ()  {
                  console.log(
                    $filter('translate')('hpo.data.exchange.fail'),
                    $filter('translate')('hpo.data.exchange.updatesfail'),
                    true
                  );
                }
              );
            }
          }
        });

        var removeFromSelection = function (obj) {
          var idx = -1;
          if (scope.res) {
            idx = scope.res.map(function (x) {
              return x.id;
            }).indexOf(obj.id);
            if (idx !== -1) scope.res.splice(idx, 1);
          }
        };

        var getElementHpoFromTable = function (result) {
          var obj;
          for (var j = 0; j < scope.data.length; j++) {
            var d = scope.data[j];
            if (
              d &&
              d.properties &&
              d.properties.UID_FICHIER === result.ftiuidsource &&
              d.properties.TYPE_FICHIER === result.filetype
            ) {
              obj = d;
              break;
            }
          }
          return obj;
        };

        scope.encodeAffichage = function (x) {
          if (x) return gaUrlUtils.tryDecodeURIComponent(x);
          else {
            return x;
          }
        };

        scope.debloquerDossierANC = function (obj) {
          scope.dossier = obj;
          swal(
            {
              title: 'Attention',
              text:
                'Si vous débloquez ce dossier, il ne fera plus partie de la tournée. Êtes vous sûr de vouloir continuer ?',
              type: 'warning',
              showCancelButton: true,
              confirmButtonColor: '#DD6B55',
              confirmButtonText: $filter('translate')('common.yes'),
              cancelButtonText: $filter('translate')('common.no'),
              closeOnConfirm: true,
            },
            function (isConfirm) {
              if (isConfirm) {
                if (scope.dossier.id.match('anc')) {
                  AncAppFactory.debloqueDossier(scope.dossier.id).then(
                    scope.debloqueDossierResponse
                  );
                } else {
                  BacAppFactory.debloqueDossier(scope.dossier.id).then(
                    scope.debloqueDossierResponse
                  );
                }
              }
            }
          );
        };
        (scope.debloqueDossierResponse = function (res) {
          if (res && res.data && typeof res.data == 'boolean') {
            scope.dossier.properties.tournee = false;
            $timeout(function () {
              swal({
                title: $filter('translate')('model.featuretypes.actions.folderUnlocked'),
                type: 'success',
                showCancelButton: false,
                closeOnConfirm: true,
              });
            }, 500);
          } else
            $timeout(function () {
              swal({
                title: 'Information',
                text: "Echec de l'action",
                type: 'error',
                showCancelButton: false,
              });
            }, 500);
        }),
        function () {
          $timeout(function () {
            swal({
              title: 'Information',
              text: "Echec de l'action",
              type: 'error',
              showCancelButton: false,
            });
          }, 500);
        };

        scope.setHour = function (att, value) {
          var hour = 'hour';
          var date = 'date';
          var date_heure = 'date_heure';

          if (value == 'valuemax') {
            hour = 'hourmax';
            date = 'datemax';
            date_heure = 'date_heure_max';
          }

          if (
            angular.isDefined(scope.criterias[att][hour]) &&
            scope.criterias[att][hour] == null
          )
            delete scope.criterias[att][hour];

          if (
            angular.isDefined(scope.criterias[att][date]) &&
            angular.isDefined(scope.criterias[att][hour])
          )
            scope.criterias[att][date_heure] =
              scope.criterias[att][date] + ' ' + scope.criterias[att][hour];
          else scope.criterias[att][date_heure] = scope.criterias[att][date];

          scope.criterias[att][value] = $filter('date')(
            moment(new Date(scope.criterias[att][date_heure])).toDate(),
            'yyyy-MM-ddTHH:mm:ss.sss'
          );
        };

        scope.getChart = function () {
          var newScope = $rootScope.$new();
          newScope.fti = angular.copy(scope.fti);
          if (scope.attributes)
            newScope.fti.attributes = angular.copy(scope.attributes);

          newScope.fti.attributes = HpoCarteAppFactory.getAppFactory().getAuthorizedAttributes(
            newScope.fti
          );

          if (scope.supplementAttributes)
            newScope.fti.attributes = newScope.fti.attributes.concat(
              scope.supplementAttributes
            );
          if (scope.geojsondata) {
            newScope.geoj = angular.copy(scope.geojsondata);
            newScope.selected = true;
          } else {
            newScope.selected = false;
            var filter = '';

            if (!angular.isUndefined(scope.where)) filter = scope.where;

            if (scope.whereFilter != '') {
              if (filter != '') filter += ' AND ';
              filter += ' ' + scope.whereFilter;
            }
            newScope.wheretab = filter;
          }

          if (!newScope.wheretab) newScope.wheretab = '';
          if (scope.hpoInfoNames) newScope.hpoInfoNames = scope.hpoInfoNames;
          ngDialog.open({
            template:
              'js/XG/widgets/hpoapp/home_admin_data/views/dialog/hpo_charts.html',
            className: 'ngdialog-theme-plain width1100 nopadding miniclose',
            closeByDocument: false,
            scope: newScope,
            draggable: {
              title: $filter('translate')('layermanager.charts'),
            },
          });
        };

        /**
         *    Quand un objet est actualisé parceque l'action l'a relu en base
         *  par l'action "getFeatureFromDb", et que l'objet est affiché dans
         *  le tableau, on met à jour la valeur de ses attributs.
         */
        scope.$on('refreshFeature', function (event, params) {
          var theRes;
          if (
            scope.res &&
            scope.res.length != 0 &&
            scope.res[0].id == params.refreshedFeature.id
          ) {
            //-- Il s'agit de l'objet sélectionné dans la datatable.
            theRes = scope.res[0];
          } else if (scope.data) {
            //-- S'agit-il d'un des bjets affiché dans la datatable ?
            for (var ind = 0; ind < scope.data.length; ind++) {
              if (scope.data[ind].id == params.refreshedFeature.id) {
                theRes = scope.data[ind];
                break;
              }
            }
          }
          if (theRes) {
            for (var prop in params.refreshedFeature.properties) {
              theRes.properties[prop] =
                params.refreshedFeature.properties[prop];
            }
          }
        });
        /**
         * Filtres sur les prochains controles
         */
        var extractMTextFilterProchainControle = function (criteriaValue) {
          let mTextFilterProchainControle = ' AND ';
          /**
           * Filtres sur les prochains controles crées
           */
          let existEncours = false;
          let existACreer = false;
          let mTextFilterEncours = " prochain_controle IN ('";
          let mTextFilterACreer
            = " ( prochain_controle is null and  type_prochain_controle_preconise IN ('";
          for (let criteria of criteriaValue) {
            if (criteria.controle_en_cours === 'Contrôles en cours') {
              existEncours = true;
              mTextFilterEncours =
                mTextFilterEncours + criteria.typeControle + "','";
            }
            if (criteria.controle_en_cours === 'Contrôles à créer') {
              existACreer = true;
              mTextFilterACreer =
                mTextFilterACreer + criteria.typeControle + "','";
            }
          }
          mTextFilterEncours = mTextFilterEncours + "') ";
          mTextFilterACreer = mTextFilterACreer + "')) ";

          if (existEncours) {
            mTextFilterProchainControle =
              mTextFilterProchainControle + mTextFilterEncours;
          }
          if (existACreer) {
            if (existEncours) {
              mTextFilterProchainControle =
                mTextFilterProchainControle + ' OR ';
            }
            mTextFilterProchainControle =
              mTextFilterProchainControle + mTextFilterACreer;
          }
          return mTextFilterProchainControle;
        };

        /**
         * takes a "feature.100" and output the number alone: "100"
         * @param {string} completeFid
         * @returns just the id number
         */
        scope.formatFid = function (completeFid) {
          let indexOfPoint = completeFid.indexOf('.');
          return completeFid.substring(indexOfPoint+1);
        };


        /**
         * get the max lenth of the attribute in the fti
         * @param {string} attributeName
         * @returns the max length
         */
        scope.getMaxLength = (attributeName) => {
          if ($rootScope.xgos.sector !=='anc'
            && $rootScope.xgos.sector !== 'bac') {
            let attributeConfig = scope.fti.attributes.find(
              attribute => attribute.name === attributeName);
            if (attributeConfig && attributeConfig.size) {
              return attributeConfig.size;
            }
          }
          return undefined;
        };


        /**
         * Stoppe la répétition des appels API en cas d'erreur dans la récupération des données
         * @param res retour du back contenant le processus
         * @param repeat intervalle déclencheur des appels API
         */
        const getDataFailed = (res, repeat) => {
          $interval.cancel(repeat);
          require('toastr').error(
            $filter('translate')('tools.majicedigeo.edigeo.loadfailed')
          );
          scope.conversionIsRunning = false;
        };

        /**
         * @returns true if FID should be displayed, false otherwise
         */
        scope.shouldDisplayFid = () => {
          return !(scope.isEsri || scope.removeFid);
        };


        /**
         * Défini la variable isEsri true quand le fti issu du ftid est de type esri.
         * Permet de filtrer sur le champ ID de la datatable
         * dans le panel de listing des données d'un composant
         */
        scope.$watch('ftid', () => {
          if (scope.ftid) {
            const fti = FeatureTypeFactory.getFeatureByUid(scope.ftid);
            if (fti) {
              scope.isEsri = fti.type === 'esri';
            }
          }
        });

        /**
         * Défini la variable isEsri true quand le fti est de type esri.
         * Permet de filtrer sur le champ ID de la datatable
         * dans les interventions simples et tables simples
         */
        scope.$watch('fti', () => {
          if (scope.fti) {
            scope.isEsri = scope.fti.type === 'esri';
          }
        });

        scope.$on('showDatatableLoader', () => {
          scope.displayLoader = true;
        });

        scope.$on('hideDatatableLoader', () => {
          scope.displayLoader = false;
        });

        /**
         * Avant recharge de la table après saisie d'un filtre sur un champ restreint par une table,
         * supprime les jokers autour de la valeur recherché pour avoir une correspondance stricte
         * avec la clé de la table de restriction.<br>
         * ex. si on tape directement "1" dans l'input on recherche uniquement les enregistrements
         * de la table dont l'id est 1 et sont exclus les enregistrements dont l'id est 11, 12...
         * @param where clause de filtre de la datatable.
         *              Exemple: <code>"1=1 AND MATERIAU ILIKE '%1%'"</code>
         * @return {string} clause where sans joker autour de la clé de table de restriction
         * @see <a href="https://altereo-informatique.atlassian.net/browse/KIS-3188">
         *      KIS-3188 - [MAP] : Usage des filtres - condition EGAL et non CONTIENT par défaut</a>
         */
        const removeWildcardIfRestrictedKey = (where) => {

          const whereCriterias = where.split(' ');
          for (let i = 0; i < whereCriterias.length - 1; i++) {

            // recherche dans la clause where les noms d'attributs basés sur une restriction Tables
            const attribute = scope.fti.attributes.find(attr => attr.name === whereCriterias[i]);
            const isRestrictedAttribute = attribute !== undefined
              && Array.isArray(attribute.restrictions)
              && attribute.restrictions.length > 0 && attribute.restrictions[0].type === 'Tables';

            // enlève les jokers autour de la valeur à la suite du nom d'attribut
            // exemple: "MATERIAU ILIKE '%1%'" => MATERIAU ILIKE '1'
            if (isRestrictedAttribute && (i + 2) < whereCriterias.length) {
              whereCriterias[i + 2] = whereCriterias[i + 2].replaceAll('%','');
            }
          }
          return whereCriterias.join(' ');
        };

        /**
         * Vérifie si le fti fourni est un faux fti.
         * Dans le cas des objects cibles/sélectionnés d'une IS,
         * on crée un fake fti (puisque c'est une couche composée pas une vraie couche)
         * @return {boolean} true si le fti de la datatable courante est un faux fti.
         */
        const isFakeFti = () => {
          return scope.fti && typeof scope.fti === 'object'
          && ['ObjectsCiblesfakefti', 'selectedObjectsFti','fakePointConsommationOmegaFti'].includes(scope.fti.name);
        };


        // Permet l'utilisation ou non du bouton editFeature
        // Le timeout laisse le temps à la directive de récupérer la valeur de scope.fti
        $timeout(() => {
          initUserRightToModifyFeature();
        });

        /**
         * Affiche une alerte en cas de succès ou d'erreur de l'export
         * lorsque l'état du processus renvoyé du back est 'FINISHED' ou 'FAILED'
         * @param process processus d'export recupéré du back par checkProcessInProgress
         * @see checkProcessInProgress
         */
        scope.getResponseFile = (process) => {
          switch (process.etat) {
            case 'FINISHED':
              require('toastr').success('Export terminé avec succés');
              break;
            case 'FAILED':
              require('toastr').error(process.reason);
              break;
          }
        };


        /**
         * KIS-3656 - Export CSV
         *
         * Construit la clause WHERE pour l'export.
         * On prend en compte en particulier la propriété "initialwhereclause".
         * Celle-ci provient du requêteur via le featuretreewidget.
         * "featuretreewidget" à partir duquel on appele la datatable.
         *
         * @return {string} clause WHERE pour l'export
         */
        const getExportWhereClause = () => {
          let whereClause = scope.fullFilter;
          if (!whereClause || typeof(whereClause) !== 'string') {
            const res = builWhereClauseFromCriterias(scope.criterias);
            whereClause = '1=1';
            if (res.keysLength && res.whereFilter!=='') {
              whereClause = res.whereFilter;
            }
          }
          if (scope.initialwhereclause && scope.initialwhereclause !== '') {
            whereClause += ' AND (' + scope.initialwhereclause+')';
          }
          return whereClause;
        };


        /**
         * Exécute l'exportation des entités de couches dans un fichier au format CSV.
         *
         * @param {string} proj - Code de projection pour l'export.
         * @param {string} encoding - Encodage du texte.
         * @param {Array} attrToExport - Liste des attributs à exporter.
         * @return {Promise} Promesse contenant le résultat de l'export.
         */
        const exportLayersFeatures = (proj, encoding, attrToExport) => {
          ImportExportFactory.exportLayersFeatures('CSV', proj, scope.fti.uid, attrToExport,
            getExportWhereClause(), '', proj, encoding,
            scope.configuser.columnDelimiter).then( // Export de la couche unique
            // Le séparateur de champ est celui de la datatable
            // (outrepasse le séparateur des paramètres régionaux)
            res => {
              if (res.data.etat !== 'RUNNING' && res.data.etat !== 'FINISHED') {
                gaDomUtils.hideGlobalLoader();
                scope.exportPending = false;
              }
              else {
                scope.checkProcessInProgress(res.data, 750, false);
              }
            },
            () => {
              scope.exportPending = false;
              gaDomUtils.hideGlobalLoader();
            }
          );
        };


        /**
         * KIS-3700
         * Renvoie la liste des attributs du Fti courant qui sont de type Timestamp.
         * @returns {Array} liste des attributs de type Timestamp.
         */
        const ftiTimestampsAttributes = () => {
          const timestampAttributes = [];
          for (const att of scope.fti.attributes) {
            if (att.type==='java.lang.Timestamp') {
              timestampAttributes.push(att);
            }
          }
          return timestampAttributes;
        };


        /**
         * KIS-3700
         * Prépare les donnes geojson pour l'export CSV.
         * Si l'option d'export sans id est active, ou si des attributs de type
         * Timestamp sont présents, on duplique les objets geojson pour
         * les adapter à l'export CSV.
         * Dans le cas "doNotExportIds" l'id est supprimé.
         * Les attributs de type Timestamp sont formatés en fonction du
         * format défini par "scope.dateFormat".
         * @returns {Array} Les donnes préparées pour l'export CSV.
         */
        const prepareGeoJsonDataForCsvExport = () => {
          let theData;
          const timestamps = ftiTimestampsAttributes();
          if (scope.doNotExportIds || timestamps.length!==0) {
            // -- KIS-3700: Cas du résultat d'une requête avec join et table maître
            // -- on ne veut pas exporter l'id de la table maître, sinon,
            // -- le service d'export va se baser sur le FTI de la table maître
            // -- pour la liste des attributs à exporter et ça ne va pas correspondre
            // -- => CSV sans informations
            theData = scope.data.map(item => {
              const newItem = angular.copy(item);
              if (scope.doNotExportIds) {
                newItem.id = null;
              }
              for (const timestampAtt of timestamps) {
                newItem.properties[timestampAtt.name]
                  = $filter('date')(newItem.properties[timestampAtt.name], scope.dateFormat);
              }
              return newItem;
            });
          }
          else {
            theData = scope.data;
          }
          return theData;
        };


        /**
         * Exporte les entités de couches au format GeoJSON dans un CSV.
         * Les données à exporter sont transmises en tant que FeatureCollection (GeoJSON).
         *
         * @param {string} encoding - Encodage du texte.
         */
        const exportGeoJsonFeatures = (encoding) => {
          // -- Fabrication du GeoJSON ne contenant que les features présentes
          // -- dans la datatable (éventuellement filtrée) -> scope.data
          // -- KIS-3700
          // -- 1) gestion des IDs pour cas où table maître utilisé dans requêteur
          // -- 2) gestion des attributs de type Timestamp
          const jsonData = prepareGeoJsonDataForCsvExport();

          const geoJsonData = {
            'type': 'FeatureCollection',
            'features': jsonData,
            totalFeatures: jsonData.length
          };
          // -- Lancement de l'export CSV
          ImportExportFactory.exportfeaturecollection(geoJsonData, 'CSV',
            null, null, '', encoding, scope.configuser.columnDelimiter
          ).then(exportResult => {
            // -- Téléchargement du CSV généré par l'export
            const downloadurl = '/services/' + PortalsFactory.getPortalId() +
              '/export/downloadexportedfile?f=json' +
              '&exportedFileId=' + exportResult.data;
            window.open(downloadurl);
          },
          res => {
            require('toastr').error($filter('translate')(res.data));
          }).finally(() => {
            // -- Rendre de nouveau accessible KIS et la commande d'export.
            gaDomUtils.hideGlobalLoader();
            scope.exportPending = false;
          });
        };


        scope.exportCsvWithEncoding = () => {
          gaDomUtils.showGlobalLoader();
          scope.exportPending = true;

          // dans l'admin, il n'y a pas de map
          const proj = scope.map ? scope.map.getView().getProjection().getCode() : null;

          // encodage de texte défini dans les paramètres régionaux du portail
          const encoding = RegionalFactory.getPortalTextEncoding($rootScope.xgos.portal);
          const mapping = scope.displayAttributes.map(attr => {
            return {
              'name': attr.name,
              'include': true,
              'type': attr.type,
              'mapping': attr.alias
            };
          });

          // export de tous les attributs sans gestion de correspondance
          const attrToExport = JSON.stringify([{
            'uid': scope.fti.uid,
            'attributes': scope.displayAttributes.map(attr => attr.name).join(';'),
            'name': scope.fti.name,
            'alias': scope.fti.alias,
            'mapping': mapping,
            'filePaths': []
          }]);

          if (!scope.exportcsvgeojson) {
            exportLayersFeatures(proj, encoding, attrToExport);
          }
          else {
            exportGeoJsonFeatures(encoding);
          }
        };


        /**
         * Appel Api à intervale régulier pour évaluer l'état du processus d'export
         * @param process processus de l'appel api précédent
         * @param intervalMsec intervale entre 2 appels api en msec
         * @see ImportExportFactory.getProgressionExport
         */
        scope.checkProcessInProgress = (process, intervalMsec) => {
          if (intervalMsec === undefined) {
            intervalMsec = 750;
          }
          scope.intervalCounter = 0;

          // les dates sont gérées comme dans tout processus (cf. edigeo, itv...)
          // mais non utilisées dans le cas présent
          if (process.creation){
            process.creation = $filter('date')(new Date(process.creation), 'dd MMM yyyy HH:MM');
          }

          // fonction contenant un appel api (getProgressionExport)
          // qui est exécutée à intervale régulier (intervalMsec)
          stop = $interval(function () {
            if (process.creation){
              process.creation = new Date(process.creation).getTime();
            }
            if (process.end){
              process.end = new Date(process.end).getTime();
            }
            ImportExportFactory.getProgressionExport(process).then(
              (res) => {
                if (res.data.etat === 'FINISHED') {

                  // stoppe la répétition des appels api
                  $interval.cancel(stop);

                  // affiche l'alerte taustr succès/erreur
                  scope.getResponseFile(res.data);

                  // récupère le nom du fichier à télécharger
                  // dans la propriété "extraInfo" du processus renvoyé
                  const resultFileName = res.data.extraInfo;
                  const portalid = PortalsFactory.getPortalId();

                  // masque le spinner
                  gaDomUtils.hideGlobalLoader();

                  const downloadurl =
                        '/services/' + portalid + '/export/downloadexportedfile?f=json' +
                        '&exportedFileId=' +
                        resultFileName;

                  window.open(downloadurl);
                  scope.exportPending = false;

                } else if (scope.exportPending && res.data.etat === 'FAILED') {
                  // affiche l'alerte taustr, stoppe la répétition
                  // de l'appel api et masque le spinner
                  $interval.cancel(repeat);
                  gaDomUtils.hideGlobalLoader();
                  require('toastr').error(
                    $filter('translate')('tools.majicedigeo.edigeo.loadfailed')
                  );
                } else {

                  //-- Toujours en train de tourner
                  if (scope.intervalCounter > 100 && intervalMsec < 90000) {

                    //-- Au plus c'est long, au moins on va interroger
                    //-- le serveur fréquemment sur l'état d'avancement.
                    $interval.cancel(stop);
                    scope.checkProcessInProgress(process, 2 * intervalMsec);
                    scope.intervalCounter = 0;
                  }
                }
              },
              () => {
                gaDomUtils.hideGlobalLoader();
                $interval.cancel(stop);
                require('toastr').error(
                  $filter('translate')('tools.majicedigeo.edigeo.loadfailed')
                );
              }
            );
          }, intervalMsec);
        };


        /**
         * Récupére la hauteur de l'élement fourni en parametre.
         * Si la hauteur n'est pas récupérable sur l'élément, on regarde son parent.
         *
         * @param {*} element Elément dont on veut récupérer la hauteur
         * @returns Hauteur de l'élement en pixels
         */
        const getHeightOf = (element) => {
          if (!element) {
            return 0;
          }
          if (element.style.height.includes('px')) {
            return parseInt(element.style.height);
          }
          if (element.offsetHeight) {
            return element.offsetHeight;
          }
          return getHeightOf(element.parentElement);
        };


        scope.$on('setInitialDataTableHeight', () => {
          renderIs = datatableIsHelper.getRenderInterventionSimle(element[0]);
          if (renderIs) {
            setDatatableHeightFromIsTab(renderIs);
          }
        });


        /**
         * Appeler depuis "extendedNgDialog" lors d'un redimensionnement vertical de la popup.
         * Met à jour la hauteur de la datatable en 2 étapes:
         * -1- Mise à jour du conteneur "fixedHeaderGlobalWrapper"
         * -2- Mise à jour de la hauteur de la datatable elle même,
         *     c'est à dire de l'élément "table" HTML lui même.
         * @param event Description de l'événement du broadcast
         * @param info Informations communiquées par le broadcast
         */
        scope.$on('updateGcDataTableHeight', (event,info) => {

          if (renderIs) {
            setDatatableHeightFromIsTab();
          }
          else {
          // -- Récupération de la hauteur actuelle du "fixedHeaderGlobalWrapper"
            let height = parseInt(scope.forceHeight);
            if (!height || isNaN(height)) {
              height = 320;
            }
            // -- Application du delta entre la nouvelle hauteur et la précédente
            // -- de la popup qui contient le gcdatatable au "fixedHeaderGlobalWrapper"
            height = Math.max(height+info.heightDelta, parseInt(info.containerHeight)-100);
            scope.forceHeight = '' + height.toFixed(0) + 'px';
            // -- Application du delta sur la hauteur de de l'élément "table" HTML lui même
            const tableContainer = element[0].getElementsByTagName('table')[0].parentElement;
            if (tableContainer) {
              tableContainer.style.height = (getHeightOf(tableContainer)+info.heightDelta) + 'px';
            }
          }
        });
      }
    };
  };

  gcelement.$inject = [
    'gclayers',
    'ngTableParams',
    'QueryFactory',
    'EditFactory',
    'FeatureTypeFactory',
    '$filter',
    '$rootScope',
    '$timeout',
    'gaDomUtils',
    'gaJsUtils',
    'ngDialog',
    'gaBrowserSniffer',
    'BacAppFactory',
    'AncAppFactory',
    'AlertHpoFactory',
    '$sce',
    'HpoCarteAppFactory',
    'gaUrlUtils',
    '$interval',
    'CalendarFactory',
    'bizeditProvider',
    'mapJsUtils',
    'CommonFactory',
    '$q',
    'RolesFactory',
    'attributeRestrictionsUtils',
    'RegionalFactory',
    'ImportExportFactory',
    'PortalsFactory', 'datatableIsHelper'
  ];
  return gcelement;
});
