From 9b264aee75ec21948667d4b4f444c9cfff3dff7f Mon Sep 17 00:00:00 2001 From: "jean-philippe.bazonnais" Date: Sun, 30 Nov 2025 20:00:48 +0100 Subject: [PATCH 1/2] Fix(territories): Prevoir le mode 'DragNDrop' sur les territoires du menu Fixes #465 --- .../Controls/Territories/Territories.js | 75 +++++++++++++---- .../Controls/Territories/TerritoriesDOM.js | 81 ++++++++++++++++--- 2 files changed, 128 insertions(+), 28 deletions(-) diff --git a/src/packages/Controls/Territories/Territories.js b/src/packages/Controls/Territories/Territories.js index 33e05df7b..6bb07dcc8 100644 --- a/src/packages/Controls/Territories/Territories.js +++ b/src/packages/Controls/Territories/Territories.js @@ -165,6 +165,13 @@ class Territories extends Control { this.getContainer().classList.add("gpf-button-no-gutter"); } + // draggable des vues + var self = this; + this.createDraggableElement( + self.element.querySelector("#gpf-territories-views-listview-entries-id"), + self + ); + /** * event triggered when a territory is loaded * @@ -211,10 +218,7 @@ class Territories extends Control { var view = this._createTerritoryView(territory); if (entry && view) { this.panelTerritoriesEntriesContainer.appendChild(entry); - var panelTerritoriesViewsContainer = this.element.querySelector("#gpf-territories-views-listview-entries-id"); - if (panelTerritoriesViewsContainer) { - panelTerritoriesViewsContainer.appendChild(view); - } + this.panelTerritoriesViewsContainer.appendChild(view); var count = this.element.querySelector("#gpf-territories-views-count-id"); if (count) { var nb = this.territories.filter(t => !t.isRemoved).length + 1; @@ -224,6 +228,7 @@ class Territories extends Control { isRemoved : false, isAdded : isAdded, data : territory, + initialIndex : this.territories.length, domEntry : entry, domView : view }); @@ -249,7 +254,7 @@ class Territories extends Control { * Remove a territory * * @param {String} territory - territory id (FRA, MTQ, ...) - * @param {Boolean} [force=false] - force removal even if territory was added manually + * @param {Boolean} [force=false] - force removal * @returns {Boolean} - true|false * @public * @example @@ -261,14 +266,16 @@ class Territories extends Control { for (let i = 0; i < this.territories.length; i++) { const o = this.territories[i]; if (o.data.id === territory.data.id) { - this.territories[i].domEntry.remove(); - this.territories[i].domView.remove(); if (!force) { // on ne le supprime pas de la liste des territoires // mais on le masque uniquement this.territories[i].isRemoved = true; + this.territories[i].domEntry.classList.add("gpf-hidden"); + this.territories[i].domView.style.display = "none"; } else { // on le supprime de la liste des territoires + this.territories[i].domEntry.remove(); + this.territories[i].domView.remove(); this.territories.splice(i, 1); } found = true; @@ -402,6 +409,7 @@ class Territories extends Control { * isAdded : { Boolean }, * domView : { HTMLelment }, * domEntry : { HTMLelment }, + * initialIndex : { Number }, // index d’origine * data : { * id: "MTQ", * title: "Martinique", @@ -434,6 +442,8 @@ class Territories extends Control { this.containerTerritoriesOptions = null; /** @private */ this.panelTerritoriesEntriesContainer = null; + /** @private */ + this.panelTerritoriesViewsContainer = null; } /** @@ -460,6 +470,7 @@ class Territories extends Control { // menu views button var territoriesPanelMenuViewsDiv = this._createTerritoriesPanelMenuViewsDivElement(); territoriesPanel.appendChild(territoriesPanelMenuViewsDiv); + this.panelTerritoriesViewsContainer = territoriesPanelMenuViewsDiv.querySelector("#gpf-territories-views-listview-entries-id"); // INFO // Les territoires seront ajoutés dans ce conteneur @@ -788,23 +799,31 @@ class Territories extends Control { logger.trace(e); // On supprime tous les territoires ajoutées manuellement via le bouton "Ajouter une vue" // et on recharge la liste initiale - var territories = [...this.territories]; - for (let index = 0; index < territories.length; index++) { - const territory = territories[index]; + for (let index = 0; index < this.territories.length; index++) { + const territory = this.territories[index]; if (territory.isAdded) { + // on supprime les territoires ajoutés manuellement this.removeTerritory(territory, true); } else { // on ré-affiche les territoires non supprimés if (territory.isRemoved) { - this.panelTerritoriesEntriesContainer.appendChild(territory.domEntry); - var panelTerritoriesViewsContainer = this.element.querySelector("#gpf-territories-views-listview-entries-id"); - if (panelTerritoriesViewsContainer) { - panelTerritoriesViewsContainer.appendChild(territory.domView); - } + territory.domEntry.classList.remove("gpf-hidden"); + territory.domView.style.display = ""; territory.isRemoved = false; } } } + // reordonne la liste des territoires selon l'index initial + this.territories.sort((a, b) => a.initialIndex - b.initialIndex); + // mise à jour le DOM principal + this.panelTerritoriesEntriesContainer.innerHTML = ""; + this.territories.forEach(territory => { + this.panelTerritoriesEntriesContainer.appendChild(territory.domEntry); + }); + this.panelTerritoriesViewsContainer.innerHTML = ""; + this.territories.forEach(territory => { + this.panelTerritoriesViewsContainer.appendChild(territory.domView); + }); // mise à jour du compteur des vues var count = this.element.querySelector("#gpf-territories-views-count-id"); if (count) { @@ -857,6 +876,32 @@ class Territories extends Control { } } + /** + * ... + * @param {Event} e - ... + * @private + */ + onReorderTerritoriesViews (e) { + logger.trace(e); + + // on récupère l’ordre des vues dans le conteneur + const domViews = Array.from(this.panelTerritoriesViewsContainer.children); + // on recrée l’ordre des territoires selon les vues + const newOrderTerritories = []; + domViews.forEach(domView => { + const found = this.territories.find(territory => territory.domView === domView); + if (found) { + newOrderTerritories.push(found); + } + }); + this.territories = newOrderTerritories; + // mise à jour l’ordre des territoires dans le DOM principal + this.panelTerritoriesEntriesContainer.innerHTML = ""; + this.territories.forEach(territory => { + this.panelTerritoriesEntriesContainer.appendChild(territory.domEntry); + }); + } + }; // on récupère les méthodes de la classe DOM diff --git a/src/packages/Controls/Territories/TerritoriesDOM.js b/src/packages/Controls/Territories/TerritoriesDOM.js index 5817c8f05..a8737254a 100644 --- a/src/packages/Controls/Territories/TerritoriesDOM.js +++ b/src/packages/Controls/Territories/TerritoriesDOM.js @@ -1,3 +1,5 @@ +import Sortable from "sortablejs"; + const stringToHTML = (str) => { var support = function () { if (!window.DOMParser) { @@ -25,17 +27,37 @@ const stringToHTML = (str) => { return dom; }; -var TerritoriesDOM = { +var TerritoriesDOM = { + /** - * Add uuid to the tag ID - * @param {String} id - id selector - * @returns {String} uid - id selector with an unique id + * Add uuid to the tag ID + * @param {String} id - id selector + * @returns {String} uid - id selector with an unique id */ _addUID : function (id) { var uid = (this.uid) ? id + "-" + this.uid : id; return uid; }, + + /** + * https://github.com/SortableJS/Sortable?tab=readme-ov-file#options + * @param {*} elementDraggable - element to make draggable + * @param {*} context - context for the draggable element + * @returns {*} Sortable instance + */ + createDraggableElement : function (elementDraggable, context) { + const sortable = Sortable.create(elementDraggable, { + animation : 150, + handle : ".gpf-btn-icon-territories-draggable", + draggable : ".draggable-territories-view", + ghostClass : "gpf-territories-view-ghost", + onEnd : function (e) { + context.onReorderTerritoriesViews(e); + } + }); + return sortable; + }, /** * Main container (DOM) @@ -407,7 +429,8 @@ var TerritoriesDOM = {
-

Ce nom est déjà utilisé

+

Ce nom est déjà utilisé

+

Le nom est vide

@@ -441,6 +464,19 @@ var TerritoriesDOM = { return; } button.setAttribute("aria-pressed", "false"); + // reinit + var input = document.getElementById(inputId); + if (input) { + input.value = ""; + var emptyMessage = document.getElementById(inputId + "-message-error-empty-id"); + var duplicateMessage = document.getElementById(inputId + "-message-error-duplicate-id"); + if (emptyMessage) { + emptyMessage.classList.add("gpf-hidden"); + } + if (duplicateMessage) { + duplicateMessage.classList.add("gpf-hidden"); + } + } self.onCloseTerritoriesViewsClick(e); }, false); } @@ -460,17 +496,36 @@ var TerritoriesDOM = { e.preventDefault(); var input = document.getElementById(inputId); if (input) { - var message = document.getElementById(inputId + "-message-error-id"); var territory = self.territories.find(e => e.data.title === input.value); - if (message && input.value === "") { + var emptyMessage = document.getElementById(inputId + "-message-error-empty-id"); + var duplicateMessage = document.getElementById(inputId + "-message-error-duplicate-id"); + + // Check if input is empty + if (input.value === "") { + if (emptyMessage) { + emptyMessage.classList.remove("gpf-hidden"); + } + if (duplicateMessage) { + duplicateMessage.classList.add("gpf-hidden"); + } return; } - // validation du nom de la vue avec ceux de la liste - if (message && territory) { - message.classList.remove("gpf-hidden"); + // Check if territory with same name exists + if (territory) { + if (duplicateMessage) { + duplicateMessage.classList.remove("gpf-hidden"); + } + if (emptyMessage) { + emptyMessage.classList.add("gpf-hidden"); + } return; - } else { - message.classList.add("gpf-hidden"); + } + // Hide both error messages if valid + if (emptyMessage) { + emptyMessage.classList.add("gpf-hidden"); + } + if (duplicateMessage) { + duplicateMessage.classList.add("gpf-hidden"); } self.onAddTerritoriesViewClick(e, input.value); } @@ -598,7 +653,7 @@ var TerritoriesDOM = { var buttonId = "gpf-territories-view-entry-button-id-" + o.id; if (o) { var entry = stringToHTML(` -
+
From be1d19ed79f45abe085a00f4e44829ebfcf461b0 Mon Sep 17 00:00:00 2001 From: "jean-philippe.bazonnais" Date: Sun, 30 Nov 2025 20:03:50 +0100 Subject: [PATCH 2/2] maj version --- DRAFT_CHANGELOG.md | 1 + package.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/DRAFT_CHANGELOG.md b/DRAFT_CHANGELOG.md index 139851d84..10fdc38e4 100644 --- a/DRAFT_CHANGELOG.md +++ b/DRAFT_CHANGELOG.md @@ -19,6 +19,7 @@ Mise à jour des icônes DSFR pour les boutons pictogrammes des widgets - Contextual Menu : Ajout du getFeatureInfo dans les entrées du menu contextuel (#442) - LayerSwitcher : Gestion des noms des styles MapBox issus de cartes.gouv.fr (#455) + - Territories : Mode DragNDrop des territoires (#468) * 🔨 [Changed] diff --git a/package.json b/package.json index ada96cb15..1a492e23c 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "geopf-extensions-openlayers", "description": "French Geoportal Extensions for OpenLayers libraries", - "version": "1.0.0-beta.6-464", - "date": "24/11/2025", + "version": "1.0.0-beta.6-468", + "date": "30/11/2025", "module": "src/index.js", "directories": {}, "engines": {