geopf-extensions-openlayers
Version:
French Geoportal Extensions for OpenLayers libraries
1,324 lines (1,156 loc) • 68 kB
JavaScript
import Sortable from "sortablejs";
import checkDsfr from "../Utils/CheckDsfr";
import ToolTips from "../../Utils/ToolTips";
import Utils from "../../Utils/Helper";
import BaseLayer from "ol/layer/Base";
import { log } from "loglevel";
var LayerSwitcherDOM = {
/**
* Creation du drag and drop
*
* @param {Object} elementDraggable - Element HTML (DOM) Container
* @param {Object} context - this
*/
_createDraggableElement : function (elementDraggable, context) {
let handleClass = [".GPtitle"];
if (checkDsfr()) {
handleClass.push(".GPlayerDragNDrop");
}
const forceFallback = !!navigator.userAgent.match(/chrome|chromium|crios/i);
// Voir lien suivant pour dragndrop avec tab
// https://robbymacdonell.medium.com/refactoring-a-sortable-list-for-keyboard-accessibility-2176b34a07f4
context._sortables = [];
handleClass.forEach(handle => {
const sortable = Sortable.create(elementDraggable, {
handle : handle,
dataIdAttr : "data-sortable-id", // required to calculate the custom sort
draggable : ".draggable-layer",
ghostClass : "GPghostLayer",
animation : 200,
// Call event function on drag and drop
onEnd : function (e) {
// FIXME pas terrrible, mais il faut bien passer ce contexte...
context._onEndDragAndDropLayerClick(e);
}
});
context._sortables.push(sortable);
});
},
/**
* Fonction permettant de bouger une couche au clavier
* @param {HTMLElement} element Élément à bouger
* @param {up|down} direction Direction dans laquelle déplacer la couche
* @returns {Boolean} Vrai si l'opération a fonctionnée.
*/
_moveElement : function (element, direction) {
const sortable_list = this._sortables[0];
if (["up", "down"].includes(direction) == false) {
return false;
}
if (typeof element.dataset.sortableId == "undefined") {
return false;
}
// Attribut pour réorganiser après
let sortableId = element.dataset.sortableId;
let order = sortable_list.toArray();
let index = order.indexOf(sortableId);
// Retrait de l'objet à déplacer
order.splice(index, 1);
// Déplace la couche à la bonne position
if (direction == "down") {
order.splice(index + 1, 0, sortableId);
} else if (direction == "up") {
order.splice(index - 1, 0, sortableId);
}
// Applique l'opéaration de tri
sortable_list.sort(order, true);
// Change le zindex et envoie l'événement
this._onEndDragAndDropLayerClick({
newIndex : order.indexOf(sortableId),
});
return true;
},
/**
* Écouteur d'événement pour modifier le z-index
* @param {Boolean} up Vrai si c'est up. Faux si down.
* @param {KeyboardEvent} event Événement du clavier
*/
_onMoveElement : function (up, event) {
if (["Enter", "Space"].includes(event.code)) {
// Choisit la bonne direction
const direction = up ? "up" : "down";
const oppositeDirection = up ? "down" : "up";
event.stopPropagation();
event.preventDefault();
// Déplace l'élément dans la bonne direction
this._moveElement(event.currentTarget.closest(".draggable-layer"), direction);
// Change le focus dans le cas où c'est le premier / dernier élément
if (window.getComputedStyle(event.currentTarget).visibility == "hidden") {
event.currentTarget.parentNode.querySelector(`[data-direction=${oppositeDirection}]`).focus();
} else {
event.currentTarget.focus();
}
}
},
// ################################################################### //
// ######################### Main container ########################## //
// ################################################################### //
/**
* 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;
},
/**
* Creation du container principal (DOM)
*
* @returns {HTMLElement} container - layer switcher DOM element
*/
_createMainContainerElement : function () {
var container = document.createElement("div");
container.id = this._addUID("GPlayerSwitcher");
container.className = "GPwidget gpf-widget gpf-mobile-fullscreen gpf-widget-button";
return container;
},
/**
* Creation du container principal d"affichage des layers (DOM)
*
* @returns {HTMLElement} input - element for minimizing/maximizing the layer switcher
*/
_createMainLayersShowElement : function () {
// <!-- Hidden checkbox for minimizing/maximizing -->
var input = document.createElement("input");
input.id = this._addUID("GPshowLayersList");
input.type = "checkbox";
return input;
},
/**
* Creation du container principal des layers (DOM)
*
* @returns {HTMLElement} container - layers list container
*/
_createMainLayersElement : function () {
// ajout de la liste des layers dans le container principal
// <div id="GPlayersList" class="GPpanel">
// (...)
// </div>
var dialog = document.createElement("dialog");
dialog.id = this._addUID("GPlayersList");
dialog.className = "GPpanel gpf-panel fr-modal";
return dialog;
},
_createMainLayersDivElement : function () {
var div = document.createElement("div");
div.className = "GPpanelBody gpf-panel__body_ls fr-modal__body";
return div;
},
_createMainLayerListElement : function () {
var div = document.createElement("div");
div.className = "GPLayerListBody";
div.setAttribute("role", "list");
return div;
},
/**
* Creation du container du picto du controle (DOM)
*
* @returns {HTMLElement} label
*/
_createMainPictoElement : function () {
var self = this;
var button = document.createElement("button");
// INFO: Ajout d'une SPAN pour enlever des marges de 6px dans CHROMIUM (?!)
var span = document.createElement("span");
button.appendChild(span);
button.id = this._addUID("GPshowLayersListPicto");
button.classList.add("GPshowOpen", "GPshowAdvancedToolPicto", "GPshowLayersListPicto");
button.classList.add("gpf-btn", "gpf-btn--tertiary", "gpf-btn-icon", "gpf-btn-icon-layerswitcher");
// button.classList.add("fr-icon-stack-line");
button.classList.add("fr-btn", "fr-btn--tertiary");
button.htmlFor = this._addUID("GPshowLayersList");
button.setAttribute("aria-label", "Afficher/masquer le gestionnaire de couches");
button.setAttribute("tabindex", "0");
button.setAttribute("aria-pressed", false);
button.setAttribute("type", "button");
if (button.addEventListener) {
button.addEventListener("click", function (e) {
var status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
document.getElementById(self._addUID("GPshowLayersList")).checked = status;
if (document.getElementById(self._addUID("GPshowLayersList")).checked) {
document.getElementById(self._addUID("GPlayerInfoPanel")).classList.remove("GPlayerInfoPanelOpened", "gpf-visible");
document.getElementById(self._addUID("GPlayerInfoPanel")).classList.add("GPlayerInfoPanelClosed", "gpf-hidden");
document.getElementById(self._addUID("GPlayerStylePanel")).classList.add("GPlayerStylePanelClosed", "gpf-hidden");
document.getElementById(self._addUID("GPlayerStylePanel")).classList.remove("GPlayerStylePanelOpened", "gpf-visible");
}
self.onShowLayerSwitcherClick(e);
});
} else if (button.attachEvent) {
button.attachEvent("onclick", function (e) {
var status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
if (document.getElementById(self._addUID("GPshowLayersList")).checked) {
document.getElementById(self._addUID("GPlayerInfoPanel")).classList.remove("GPlayerInfoPanelOpened", "gpf-visible");
document.getElementById(self._addUID("GPlayerInfoPanel")).classList.add("GPlayerInfoPanelClosed", "gpf-hidden");
document.getElementById(self._addUID("GPlayerStylePanel")).classList.add("GPlayerStylePanelClosed", "gpf-hidden");
document.getElementById(self._addUID("GPlayerStylePanel")).classList.remove("GPlayerStylePanelOpened", "gpf-visible");
}
self.onShowLayerSwitcherClick(e);
});
}
return button;
},
_createMainCounterLayersElement : function () {
var span = document.createElement("span");
span.id = this._addUID("GPlayerCounter");
span.className = "GPlayerCounter";
span.innerHTML = "0";
return span;
},
/**
* Creation du container du panneau d"information (DOM)
*
* @returns {HTMLElement} container
*/
_createMainInfoElement : function () {
// gestion du panneau d"information dans le container principal
// <div id="GPlayerInfoPanel" class="GPlayerInfoPanelClosed">...</div>
var divP = document.createElement("dialog");
divP.id = this._addUID("GPlayerInfoPanel");
divP.className = "GPpanel GPlayerInfoPanelClosed gpf-panel fr-modal";
return divP;
},
_createMainInfoDivElement : function () {
var div = document.createElement("div");
div.className = "gpf-panel__body fr-modal__body";
return div;
},
/**
* Creation du container du panneau des styles (DOM)
*
* @returns {HTMLElement} container
*/
_createMainStyleElement : function () {
// gestion du panneau d"information dans le container principal
// <div id="GPlayerInfoPanel" class="GPlayerInfoPanelClosed">...</div>
var divP = document.createElement("dialog");
divP.id = this._addUID("GPlayerStylePanel");
divP.className = "GPpanel GPlayerStylePanelClosed gpf-panel fr-modal";
return divP;
},
_createMainStyleDivElement : function () {
var div = document.createElement("div");
div.className = "gpf-panel__body fr-modal__body";
return div;
},
// ################################################################### //
// ######################### Layer container ######################### //
// ################################################################### //
_createLayersPanelHeaderElement : function () {
var container = document.createElement("div");
// on n'utilise pas le dsfr !
// container.className = "GPpanelHeader gpf-panel__header fr-modal__header";
container.className = "GPpanelHeader gpf-panel__header_ls";
return container;
},
_createLayersPanelIconElement : function () {
var label = document.createElement("label");
label.className = "GPpanelIcon gpf-btn-header gpf-btn-icon-layers";
label.title = "Couches";
return label;
},
_createLayersPanelTitleElement : function () {
var div = document.createElement("div");
// on n'utilise pas le dsfr !
div.className = "GPpanelTitle gpf-panel__title_ls";
div.id = this._addUID("GPlayersHeaderTitle");
div.innerHTML = "Couches";
return div;
},
_createLayersPanelCloseElement : function () {
// contexte
var self = this;
var btnClose = document.createElement("button");
btnClose.id = this._addUID("GPlayersPanelClose");
btnClose.className = "GPpanelClose GPlayersPanelClose gpf-btn gpf-btn-icon-close fr-btn--close fr-btn fr-btn--tertiary-no-outline fr-m-1w";
btnClose.title = "Fermer le panneau";
var span = document.createElement("span");
span.className = "GPelementHidden gpf-visible fr-mx-1w"; // afficher en dsfr
span.innerText = "Fermer";
btnClose.appendChild(span);
// Link panel close / visibility checkbox
if (btnClose.addEventListener) {
btnClose.addEventListener("click", function () {
document.getElementById(self._addUID("GPshowLayersListPicto")).click();
}, false);
} else if (btnClose.attachEvent) {
btnClose.attachEvent("onclick", function () {
document.getElementById(self._addUID("GPshowLayersListPicto")).click();
});
}
return btnClose;
},
/**
* Créé le conteneur du header
* @returns {HTMLDivElement} Conteneur
*/
_createHeaderButtonsDivElement : function () {
var div = document.createElement("div");
div.className = "GPbodyHeader";
div.id = this._addUID("GPbodyHeader");
return div;
},
/**
* Créé le conteneur des boutons du header
* @param {Object} options Options
* @param {String} [options.className] ClassName de l'élément
* @param {Boolean} [options.left] Optionnel. Place les boutons à gauche si vrai.
* @param {Boolean} [options.size] Optionnel. Taille des boutons. Par défaut, 'md'.
* @param {String} [options.id] ClassName de l'élément (utilisé pour l'id aussi)
* @returns {HTMLDivElement} Contenur de bouton
*/
_createButtonsGroupElement : function (options) {
options = options ? options : {};
let customClass = options.className ? options.className : "";
let position = options.left ? "left" : "right";
let classSize = "";
options.size = options.size ? options.size : "";
switch (options.size.toLowerCase()) {
case "sm":
classSize = "fr-btns-group--sm";
break;
case "lg":
classSize = "fr-btns-group--lg";
break;
}
var div = document.createElement("div");
div.className = `${customClass} GPbtnsGroup GPbtnsGroup--${position} fr-btns-group fr-btns-group--${position} fr-btns-group--inline-reverse fr-btns-group--inline ${classSize} fr-btns-group--icon-left`;
let id = options.id !== null ? `${customClass}_ID_${options.id}` : customClass;
div.id = this._addUID(id);
return div;
},
/**
* Créé un bouton
* @param {Object} options Options du bouton (de type LayerSwitcher.HeaderButton)
* @returns {HTMLButtonElement} Bouton
*/
_createButtonHeaderElement : function (options) {
let btn = document.createElement("button");
btn.className = "fr-btn fr-btn--tertiary gpf-btn ";
if (options.className) {
btn.className += options.className;
}
if (options.icon) {
btn.className += options.icon;
}
if (options.label) {
btn.innerHTML = options.label;
}
if (options.title) {
btn.title = options.title;
btn.ariaLabel = options.title;
}
btn.id = options.id ? options.id : this._addUID("GPtools-" + options.label.toLowerCase());
if (options.attributes) {
// Attributs supplémentaires sur le bouton
for (const attribute in options.attributes) {
if (!Object.hasOwn(options.attributes, attribute)) {
continue;
}
const element = options.attributes[attribute];
btn.setAttribute(attribute, element);
}
}
let self = this;
btn.addEventListener("click", (e) => {
self._onClickHeaderButtons(e, options.label, options.cb);
});
return btn;
},
/**
* Creation du container du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Object} obj.layer - couche (ol ou leaflet)
* @param {String} obj.id - identifiant de la couche (pour ol ou leaflet)
* @param {String} obj.title - nom de la couche à afficher dans le controle
* @param {String} obj.description - description de la couche à afficher
* @param {Boolean} obj.visibility - visibilité de la couche dans la carte (true or false)
* @param {Float} obj.opacity - opacité de la couche
* @param {String} obj.type - feature or vector
* @param {Boolean} tooltips - active ou non les tooltips HTML
*
* @returns {HTMLElement} container
*/
_createContainerLayerElement : function (obj, tooltips) {
// exemple :
// <div id="GPlayerSwitcher_ID_Layer1" class="GPlayerSwitcher_layer outOfRange">
// <!-- Basic toolbar : visibility / layer name
// _createBasicToolElement
// _createVisibilityElement
// _createLayerNameElement
// -->
// <!-- Hidden checkbox + label for showing advanced toolbar
// _createAdvancedToolShowElement
// -->
// <!-- Advanced toolbar : layer info / opacity slider / opacity value / removal
// _createAdvancedToolDivElement
// _createDeleteElement
// _createInformationElement
// _createOpacityElement
// -->
// </div>
// <!-- Layer entry in layer list -->
// <!-- Every item is marked with layerID, which is defined at layer import -->
var container = document.createElement("div");
container.id = this._addUID("GPlayerSwitcher_ID_" + obj.id);
container.className = "GPlayerSwitcher_layer gpf-panel__content fr-modal__content draggable-layer";
// ajout des outils basiques (visibility / layer name)
container.appendChild(this._createBasicToolElement(obj, tooltips));
// liste des outils avancés (layer info / opacity slider / opacity value / removal)
container.appendChild(this._createAdvancedToolDivElement(obj));
container.setAttribute("tabindex", 0);
container.setAttribute("role", "listitem");
["click", "keydown"].forEach(type => {
container.addEventListener(type, (e) => {
this._onSelectLayer(e);
});
});
return container;
},
// ################################################################### //
// ############################ Layer tool ########################### //
// ################################################################### //
/**
* Creation du container des outils basiques du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Boolean} tooltips - autoriser ou non les tooltips HTML
* @returns {HTMLElement} container
*/
_createBasicToolElement : function (obj, tooltips) {
// exemple :
// <div id="GPbasicTools_ID_1" class="GPlayerBasicTools">
// <!-- _createBasicToolButtons -->
// <!-- _createAdvancedToolShowElement -->
// <!-- _createVisibilityElement -->
// <!-- _createDeleteElement -->
// <!-- _createBasicToolTitleElement -->
// <!-- _createLayerThumbnailElement -->
// <!-- _createLayerNameDivElement -->
// <!-- _createLayerNameElement -->
// <!-- _createLayerProducerElement -->
// <!-- _createDragNDropElement -->
// </div>
var div = document.createElement("div");
div.id = this._addUID("GPbasicTools_ID_" + obj.id);
div.className = "GPlayerBasicTools";
div.appendChild(this._createBasicToolButtons(obj));
div.appendChild(this._createBasicToolTitleElement(obj, tooltips));
if (obj.draggable) {
div.appendChild(this._createDragNDropElement(obj));
}
return div;
},
/**
* Creation du groupe de bouton basiques
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @returns {HTMLElement} container
*/
_createBasicToolButtons : function (obj) {
let div = document.createElement("div");
div.id = this._addUID("GPbasicToolButtons_ID_" + obj.id);
div.className = "GPbasicToolButtons";
div.appendChild(this._createAdvancedToolShowElement(obj));
div.appendChild(this._createVisibilityElement(obj));
if (obj.deletable) {
div.appendChild(this._createDeleteElement(obj.id));
}
return div;
},
/**
* Creation du container des outils basiques du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Boolean} tooltips - autoriser ou non les tooltips HTML
* @returns {HTMLElement} container
*/
_createBasicToolTitleElement : function (obj, tooltips) {
let div = document.createElement("div");
div.id = this._addUID("GPtitle_ID_" + obj.id);
div.className = "GPtitle";
div.appendChild(this._createLayerThumbnailElement(obj));
div.appendChild(this._createLayerNameDivElement(obj, tooltips));
return div;
},
/**
* Creation du container du nom de la couche.
* Ajoute le nom du producteur de donnée s'il y'en a un.
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Boolean} tooltips - autoriser ou non les tooltips HTML
* @returns {HTMLElement} container
*/
_createLayerNameDivElement : function (obj, tooltips) {
let div = document.createElement("div");
div.id = this._addUID("GPlayerTitle_ID_" + obj.id);
div.className = "GPlayerTitle";
div.appendChild(this._createLayerNameElement(obj, tooltips));
div.appendChild(this._createLayerProducerElement(obj, tooltips));
return div;
},
/**
* Creation du container des outils basiques du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {String} obj.thumbnail - Pictogramme de la couche (url ou fichier statique)
* @returns {HTMLElement} container
*/
_createLayerThumbnailElement : function (obj) {
let img = document.createElement("img");
img.id = this._addUID("GPtitleImage_ID_" + obj.id);
img.className = "GPtitleImage GPtitleDefaultImage";
img.alt = "";
if (obj.thumbnail && typeof obj.thumbnail === "string" && obj.thumbnail !== "default") {
img.classList.remove("GPtitleDefaultImage");
img.src = obj.thumbnail;
}
return img;
},
/**
* Creation du nom du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Boolean} tooltips - active ou non les tooltips
* @returns {HTMLElement} container
*/
_createLayerNameElement : function (obj, tooltips) {
// exemple :
// <span id="GPname_ID_Layer1" class="GPlayerName" title="Quartiers prioritaires de la ville">Quartiers prioritaires de la ville</span>
var label = document.createElement("div");
label.id = this._addUID("GPname_ID_" + obj.id);
label.className = "GPlayerName";
label.title = obj.description || obj.title;
if (tooltips) {
label.dataset.tooltip = obj.description || obj.title;
ToolTips.active(label);
label.title = obj.name;
}
label.innerHTML = obj.title;
if (obj.layer.config && obj.layer.config.serviceParams.id === "GPP:TMS") {
label.innerHTML = obj.description;
}
return label;
},
/**
* Creation du container des outils basiques du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @param {Boolean} tooltips - autoriser ou non les tooltips HTML
* @returns {HTMLElement} container
*/
_createLayerProducerElement : function (obj, tooltips) {
let div = document.createElement("div");
div.id = this._addUID("GPlayerProducer_ID_" + obj.id);
div.className = "GPlayerProducer";
div.innerHTML = obj.producer;
if (tooltips) {
div.dataset.tooltip = obj.producer;
ToolTips.active(div);
}
return div;
},
_createDragNDropElement : function (obj) {
// INFO inactif en mode classique !
let button = document.createElement("div");
button.id = this._addUID("GPdragndropPicto_ID_" + obj.id);
button.className = "GPelementHidden GPlayerDragNDrop gpf-btn gpf-btn-icon-ls-draggable gpf-btn--tertiary";
button.title = "Deplacer la couche";
// button.setAttribute("tabindex", "0");
let self = this;
// Boutons pour déplacer la couche au clavier
let divKeyboard = document.createElement("div");
divKeyboard.className = "keyboard-navigation";
let spanUp = document.createElement("span");
spanUp.tabIndex = 0;
spanUp.dataset.direction = "up";
spanUp.title = spanUp.ariaLabel = "Déplacer la couche vers le haut";
spanUp.className = "fr-icon-arrow-up-line fr-icon--sm";
spanUp.onkeydown = this._onMoveElement.bind(this, true);
let spanDown = document.createElement("span");
spanDown.tabIndex = 0;
spanDown.dataset.direction = "down";
spanDown.title = spanDown.ariaLabel = "Déplacer la couche vers le bas";
spanDown.className = "fr-icon-arrow-down-line fr-icon--sm";
spanDown.onkeydown = this._onMoveElement.bind(this, false);
divKeyboard.appendChild(spanDown);
divKeyboard.appendChild(spanUp);
button.appendChild(divKeyboard);
if (button.addEventListener) {
button.addEventListener("click", function (e) {
self._onStartDragAndDropLayerClick(e);
});
} else if (button.attachEvent) {
button.attachEvent("onclick", function (e) {
self._onStartDragAndDropLayerClick(e);
});
}
return button;
},
/**
* Creation de l'icone de visibilité du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
* @returns {HTMLElement[]} array containing input and label elements
*/
_createVisibilityElement : function (obj) {
var visible = (typeof obj.visibility !== "undefined") ? obj.visibility : true;
let button = document.createElement("button");
button.id = this._addUID("GPvisibilityPicto_ID_" + obj.id);
let className = "gpf-btn-icon-ls-visibility gpf-btn-icon";
if (checkDsfr()) {
className = visible ? "fr-icon-eye-line" : "fr-icon-eye-off-line";
}
button.className = `GPlayerVisibility gpf-btn ${className} fr-btn fr-btn--sm gpf-btn--tertiary fr-btn--tertiary-no-outline`;
button.title = "Afficher/masquer la couche";
button.setAttribute("tabindex", "0");
button.setAttribute("aria-pressed", visible);
button.setAttribute("type","button");
var context = this;
let onClick = function (e) {
var status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
if (checkDsfr()) {
button.classList.toggle("fr-icon-eye-off-line", status);
button.classList.toggle("fr-icon-eye-line", !status);
}
context._onVisibilityLayerClick(e);
};
if (button.addEventListener) {
button.addEventListener("click", onClick);
} else if (button.attachEvent) {
button.attachEvent("onclick", onClick);
}
return button;
},
/**
* Creation de l'affichage du menu des outils avancés du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
*
* @returns {HTMLElement[]} array containing input and label elements
*/
_createAdvancedToolShowElement : function (obj) {
let button = document.createElement("button");
button.id = this._addUID("GPshowAdvancedTools_ID_" + obj.id);
button.className = "GPshowAdvancedToolPicto GPshowMoreOptionsImage GPshowMoreOptions GPshowLayerAdvancedTools gpf-btn fr-icon-arrow-down-s-line fr-btn--sm fr-btn--tertiary-no-outline";
button.title = "Plus d'outils";
button.setAttribute("tabindex", "0");
button.setAttribute("aria-pressed", false);
button.setAttribute("type", "button");
let self = this;
const fn = (e) => {
let status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
let element = document.getElementById(self._addUID("GPadvancedTools_ID_" + obj.id));
if (status) {
element.classList.replace("GPelementVisible", "GPelementHidden");
element.classList.replace("gpf-visible", "gpf-hidden");
} else {
element.classList.replace("GPelementHidden", "GPelementVisible");
element.classList.replace("gpf-hidden", "gpf-visible");
}
};
if (button.addEventListener) {
button.addEventListener("click", fn);
} else if (button.attachEvent) {
button.attachEvent("onclick", fn);
}
return button;
},
/**
* Creation du container des outils avancés du layer (DOM)
*
* @param {Object} obj - options de la couche à ajouter dans le layer switcher
*
* @returns {HTMLElement} container
*/
_createAdvancedToolDivElement : function (obj) {
// exemple :
// <div id="GPadvancedTools_ID_Layer1" class="GPlayerAdvancedTools">
// <!-- _createDeleteElement -->
// <!-- _createInformationElement -->
// <!-- _createOpacityElement -->
// </div>
let container = document.createElement("div");
container.id = this._addUID("GPadvancedTools_ID_" + obj.id);
container.className = "GPelementHidden GPlayerAdvancedTools gpf-hidden";
// Opacité
let opacity = document.createElement("div");
opacity.id = this._addUID("GPopacityContainer_ID_" + obj.id);
opacity.className = "GPopacityContainer";
let array = this._createOpacityElement(obj.id, obj.opacity);
for (let i = 0; i < array.length; i++) {
opacity.appendChild(array[i]);
}
container.appendChild(opacity);
let btnGroups = this._createButtonsGroupElement({
className : "GPAdvancedToolBtnsGroup",
id : obj.id,
left : true,
size : "sm",
});
// Boutons d'actions
if (obj.advancedTools && obj.advancedTools instanceof Array) {
// Récupère les boutons préconfigurés
const switcherButtons = this.constructor.switcherButtons;
// N'ajoute aucun élément si un objet vide a été mis
// Diffère de l'action par défaut (bouton d'info)
if (!obj.advancedTools.length) {
return;
}
obj.advancedTools.forEach(tool => {
const key = tool.key;
if (key && Object.values(switcherButtons).includes(key)) {
let fn;
// TODO : mettre cela dans une variable statique privée ?
switch (key) {
case switcherButtons.INFO:
fn = this._createInformationElement;
break;
case switcherButtons.EDIT:
fn = this._createEditionElement;
break;
case switcherButtons.GREYSCALE:
fn = this._createGreyscaleElement;
break;
case switcherButtons.EXTENT:
fn = this._createExtentElement;
break;
}
// Si une fonction a bien été trouvée, on créé le bouton qui va avec
if (typeof fn === "function") {
btnGroups.appendChild(fn.call(this, obj, tool));
}
} else if (tool) {
btnGroups.appendChild(this._createAdvancedToolElement(obj, tool));
}
});
container.appendChild(btnGroups);
} else {
if (checkDsfr()) {
btnGroups.appendChild(this._createInformationElement(obj, {}));
btnGroups.appendChild(this._createEditionElement(obj, {}));
btnGroups.appendChild(this._createGreyscaleElement(obj, {}));
btnGroups.appendChild(this._createExtentElement(obj, {}));
} else {
btnGroups.appendChild(this._createInformationElement(obj, {}));
btnGroups.appendChild(this._createGreyscaleElement(obj, {}));
}
container.appendChild(btnGroups);
}
return container;
},
/**
* Configure le bouton selon les options du bouton.
*
* @param {HTMLButtonElement} button Bouton à configurer
* @param {Object} tool Option du bouton (override les valeurs par défaut)
* (Objet de type AdvancedToolOption)
* @param {Boolean} [setClick] Optionnel. Indique si une fonction au clic doit être ajoutée.
* Vrai par défaut.
* @returns {HTMLButtonElement} Bouton donné en paramètre
*/
_setAdvancedToolOptions : function (button, tool, setClick = true) {
if (!button) {
return;
}
let label;
if (tool.label) {
label = tool.label.toLowerCase().replaceAll(" ", "-");
} else {
label = tool.icon;
}
const iconClass = `gpf-btn-icon-ls-tools-${label}`;
if (tool.className) {
button.className += ` ${tool.className} `;
}
if (tool.icon) {
this._setButtonIconStyle(button, iconClass, tool.icon);
}
if (tool.label) {
button.innerHTML = tool.label;
}
if (tool.title) {
button.title = tool.title;
button.ariaLabel = tool.title;
}
if (tool.attributes) {
// Attributs supplémentaires sur le bouton
for (const attribute in tool.attributes) {
if (!Object.hasOwn(tool.attributes, attribute)) {
continue;
}
const element = tool.attributes[attribute];
button.setAttribute(attribute, element);
}
}
button.setAttribute("tabindex", "0");
button.setAttribute("type", "button");
let self = this;
if (setClick) {
button.addEventListener("click", (e) => {
self._onClickAdvancedToolsMore(e, tool.label, tool.cb);
});
}
return button;
},
/**
* Ajoute une icône personnalisée au bouton
*
* @param {HTMLButtonElement} button Bouton à modifier
* @param {String} iconClass Classe de l'icône. Peut être une balise svg ou une classe.
* @param {String} icon Icône en paramètre de l'option avancée
*/
_setButtonIconStyle : function (button, iconClass, icon) {
let svg = false;
const regex = /(\.|\\)/;
if (icon) {
if (icon.startsWith("<svg")) {
// FIXME
// width / height à definir si ces options ne sont pas renseignées inline
icon = "data:image/svg+xml;base64," + btoa(icon);
svg = true;
} else if (!regex.test(icon)) {
// L'icône n'est pas une URL
button.className += icon;
} else {
// On ajoute l'URL en style après
svg = true;
}
}
// Ajoute le style SVG
if (svg) {
// Ajoute la classe au bouton
button.classList.add(iconClass);
if (!document.querySelector(`style[data-injected="${iconClass.toLowerCase()}"]`)) {
// Ajoute le style au document s'il n'existe pas encore
const style = document.createElement("style");
style.dataset.injected = iconClass.toLowerCase();
style.textContent = `
.${iconClass.toLowerCase()}::before {
width: 100%;
height: 100%;
-webkit-mask-image: url('${icon}');
-webkit-mask-repeat: no-repeat;
-webkit-mask-position: center;
mask-image: url('${icon}');
mask-repeat: no-repeat;
mask-position: center;
}
`;
document.head.appendChild(style);
}
}
},
/**
* Creation de l'icone de suppression du layer (DOM)
*
* @param {String} id - ID de la couche à ajouter dans le layer switcher
*
* @returns {HTMLElement} container
*/
_createDeleteElement : function (id) {
let button = document.createElement("button");
button.id = this._addUID("GPremove_ID_" + id);
// Icône et type de bouton
let className = "gpf-btn-icon-ls-remove gpf-btn-icon";
if (checkDsfr()) {
className = "fr-icon-delete-line";
}
button.className = `GPlayerRemove gpf-btn ${className} fr-btn fr-btn--sm gpf-btn--tertiary fr-btn--tertiary-no-outline`;
button.title = "Supprimer la couche";
button.layerId = id;
button.setAttribute("tabindex", "0");
button.setAttribute("type", "button");
var context = this;
if (button.addEventListener) {
button.addEventListener("click", function (e) {
context._onDropLayerClick(e);
});
} else if (button.attachEvent) {
button.attachEvent("onclick", function (e) {
context._onDropLayerClick(e);
});
}
return button;
},
/**
* Creation de l'icone d'edition du layer (DOM)
*
* @param {Object} obj - Objet de la couche à configurer
* @param {String} obj.id - ID de la couche à ajouter dans le layer switcher
* @param {Boolean} obj.editable - mode editable
* @param {Boolean} obj.tms - tms ou non
* @param {Array} obj.styles - styles des tms
* @param {Object} tool Option du bouton (override les valeurs par défaut)
* (Objet de type AdvancedToolOption)
*
* @returns {HTMLElement} container
*/
_createEditionElement : function (obj, tool) {
let id = obj.id;
let editable = obj.editable;
let tms = (obj.layer.config && obj.layer.config.serviceParams.id === "GPP:TMS");
let styles = tms ? obj.layer.config.styles : null;
let button = document.createElement("button");
button.id = this._addUID("GPedit_ID_" + id);
button.layerId = id;
// Options du bouton
let icon = "gpf-btn-icon-ls-edit gpf-btn-icon";
let label;
if (checkDsfr()) {
icon = "fr-icon-pencil-line";
label = "Style";
}
let className = `GPlayerEdit gpf-btn gpf-btn--tertiary fr-btn fr-btn--tertiary-no-outline`;
const options = {
icon : icon,
label : label,
};
// Override les fonctions par défaut
const toolOptions = Utils.assign(options, tool);
// Ajoute la classe et n'écrase pas les classes nécessaires
toolOptions.className = className;
// Ajoute la classe et n'écrase pas les classes nécessaires
if (tool && tool.className) {
toolOptions.className += ` ${tool.className}`;
}
// Mets les attributs du bouton
this._setAdvancedToolOptions(button, toolOptions, false);
// hack pour garder un emplacement vide en mode desktop
// et cacher l'entrée en mode mobile
if (!editable || (tms && styles.length === 1)) {
button.disabled = true;
}
let context = this;
if (tms && styles.length > 1) {
if (button.addEventListener) {
button.addEventListener("click", function (e) {
context._onEditLayerStyleClick(e, styles);
});
} else if (button.attachEvent) {
button.attachEvent("onclick", function (e) {
context._onEditLayerStyleClick(e, styles);
});
}
} else {
if (button.addEventListener) {
button.addEventListener("click", function (e) {
context._onEditLayerClick(e);
});
} else if (button.attachEvent) {
button.attachEvent("onclick", function (e) {
context._onEditLayerClick(e);
});
}
}
return button;
},
/**
* Creation de l'icone d'information du layer (DOM)
*
* @param {Object} obj - Objet de la couche à configurer
* @param {String} obj.id - ID de la couche à ajouter dans le layer switcher
* @param {String} obj.title - titre
* @param {String} obj.description - description
* @param {Object} tool Option du bouton (override les valeurs par défaut)
* (Objet de type AdvancedToolOption)
*
* @returns {HTMLElement} container
*/
_createInformationElement : function (obj, tool) {
// exemple :
// <div id="GPinfo_ID_Layer1" class="GPlayerInfo" title="Informations/légende" onclick="GPopenLayerInfo(this);"></div>
let id = obj.id;
let title = obj.title;
let description = obj.description;
let button = document.createElement("button");
button.id = this._addUID("GPinfo_ID_" + id);
button.layerId = id;
// Options du bouton
let icon = "gpf-btn-icon-ls-info gpf-btn-icon";
let label;
if (checkDsfr()) {
icon = "fr-icon-information-line";
label = "Infos";
}
let className = `GPlayerInfo GPlayerInfoClosed gpf-btn gpf-btn--tertiary fr-btn fr-btn--tertiary-no-outline`;
const options = {
icon : icon,
label : label,
};
const toolOptions = Utils.assign(options, tool);
toolOptions.className = className;
// Ajoute la classe et n'écrase pas les classes nécessaires
if (tool && tool.className) {
toolOptions.className += ` ${tool.className}`;
}
// Permet d'override les valeurs par défaut du bouton
this._setAdvancedToolOptions(button, toolOptions, false);
// button.title = "Informations/légende";
if (!title || !description) {
button.disabled = true;
}
// add event on click
var context = this;
if (button.addEventListener) {
button.addEventListener(
"click",
function (e) {
context._onOpenLayerInfoClick(e);
}
);
} else if (button.attachEvent) {
// internet explorer
button.attachEvent(
"onclick",
function (e) {
context._onOpenLayerInfoClick(e);
}
);
}
return button;
},
/**
* Creation de l'icone de n&b du layer (DOM)
*
* @param {Object} obj - Objet de la couche à configurer
* @param {String} obj.id - ID de la couche à ajouter dans le layer switcher
* @param {Boolean} obj.grayable - le mode grisable est il possible pour ce type de couche
* @param {Boolean} obj.grayscale - option grisée de la couche
* @param {Object} tool Option du bouton (override les valeurs par défaut)
* (Objet de type AdvancedToolOption)
*
* @returns {HTMLElement} container
*/
_createGreyscaleElement : function (obj, tool) {
// exemple :
// <div id="GPgreyscale_ID_Layer1" class="GPlayerBreyscale" title="Noir & blanc" onclick="GPtoggleGreyscale(this);"></div>
let id = obj.id;
let grayable = obj.grayable;
let grayscale = obj.grayscale;
tool = tool ? tool : {};
var _grayscale = (typeof grayscale !== "undefined") ? grayscale : false;
let button = document.createElement("button");
button.id = this._addUID("GPgreyscale_ID_" + id);
button.layerId = id;
// Options du bouton
let icon = "gpf-btn-icon-ls-greyscale gpf-btn-icon";
let label;
if (checkDsfr()) {
icon = "fr-icon-contrast-line";
label = "Noir et blanc";
}
let className = `GPlayerGreyscale GPlayerGreyscaleOff gpf-btn gpf-btn--tertiary fr-btn fr-btn--tertiary-no-outline`;
const options = {
icon : icon,
label : label,
};
const toolOptions = Utils.assign(options, tool);
toolOptions.className = className;
// Ajoute la classe et n'écrase pas les classes nécessaires
if (tool && tool.className) {
toolOptions.className += ` ${tool.className}`;
}
// Permet d'override les valeurs par défaut du bouton
this._setAdvancedToolOptions(button, toolOptions, false);
if (_grayscale) {
button.classList.replace("GPlayerGreyscaleOff", "GPlayerGreyscaleOn");
}
button.setAttribute("aria-pressed", _grayscale);
// hack pour garder un emplacement vide
if (!grayable) {
button.disabled = true;
}
// add event on click
var context = this;
if (button.addEventListener) {
button.addEventListener(
"click",
function (e) {
var status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
context._onToggleLayerGreyscaleClick(e);
}
);
} else if (button.attachEvent) {
// internet explorer
button.attachEvent(
"onclick",
function (e) {
var status = (e.target.ariaPressed === "true");
e.target.setAttribute("aria-pressed", !status);
context._onToggleLayerGreyscaleClick(e);
}
);
}
return button;
},
/**
* Creation de l'icone de gestion de l'opacité du layer (DOM)
*
* @param {String} id - ID de la couche à ajouter dans le layer switcher
* @param {Number} opacity - Valeur de l'opacité
*
* @returns {HTMLElement[]} Tableau de 2 containers
*/
_createOpacityElement : function (id, opacity) {
// exemple :
// <div id="GPopacity_ID_Layer1" class="GPlayerOpacity" title="Opacité">
// <input id="GPopacityRange_ID_Layer1" type="range" value="100" oninput="GPchangeLayerOpacity(this);" onchange="GPchangeLayerOpacity(this);">
// </div>
// <div class="GPlayerOpacityValue" id="GPopacityValueDiv_ID_Layer1">
// <span id="GPopacityValue_ID_Layer1">100</span>
// %
// </div>
var list = [];
// curseur pour changer l'opacité
var divO = document.createElement("div");
divO.id = this._addUID("GPopacity_ID_" + id);
divO.className = "GPlayerOpacity fr-range fr-range--sm";
// For DSFR
divO.dataset.frJsRange = "true";
divO.title = "Opacité";
var _opacity = (typeof opacity !== "undefined") ? opacity : 1;
_opacity = Math.round(_opacity * 100);
divO.style.setProperty("--progress-right", _opacity + "%");
var input = document.createElement("input");
input.id = this._addUID("GPopacityValueDiv_ID_" + id);
input.type = "range";
input.value = _opacity;
input.ariaLabel = "Opacité";
// add event for opacity change
var context = this;
if (input.addEventListener) {
input.addEventListener(
"change",
function (e) {
context._onChangeLayerOpacity(e);
}
);
} else if (input.attachEvent) {
// internet explorer
input.attachEvent(
"onchange",
function (e) {
context._onChangeLayerOpacity(e);
}
);
}
if (input.addEventListener) {
input.addEventListener(
"input",