UNPKG

geopf-extensions-openlayers

Version:

French Geoportal Extensions for OpenLayers libraries

1,138 lines (1,023 loc) 111 kB
// import CSS import "../../CSS/Controls/SearchEngine/GPFsearchEngine.css"; // import "../../CSS/Controls/SearchEngine/GPFsearchEngineStyle.css"; // import OpenLayers // import Control from "ol/control/Control"; import Control from "../Control"; import Widget from "../Widget"; import Overlay from "ol/Overlay"; import { transform as olProjTransform, get as olProjGet, transformExtent as olProjTransformExtent } from "ol/proj"; // import geoportal library access import Gp from "geoportal-access-lib"; // import local import Config from "../../Utils/Config"; import Logger from "../../Utils/LoggerByDefault"; import Utils from "../../Utils/Helper"; import Markers from "../Utils/Markers"; import Interactions from "../Utils/Interactions"; import SelectorID from "../../Utils/SelectorID"; import MathUtils from "../../Utils/MathUtils"; import SearchEngineUtils from "../../Utils/SearchEngineUtils"; import GeocodeUtils from "../../Utils/GeocodeUtils"; import CRS from "../../CRS/CRS"; // import local des layers import GeoportalWMS from "../../Layers/LayerWMS"; import GeoportalWMTS from "../../Layers/LayerWMTS"; import GeoportalWFS from "../../Layers/LayerWFS"; import GeoportalMapBox from "../../Layers/LayerMapBox"; // Service import Search from "../../Services/Search"; // DOM import SearchEngineDOM from "./SearchEngineDOM"; import checkDsfr from "../Utils/CheckDsfr"; var logger = Logger.getLogger("searchengine"); /** * @classdesc * SearchEngine control * * @constructor * @extends {ol.control.Control} * @type {ol.control.SearchEngine} * @alias ol.control.SearchEngine * @param {Object} options - control options * @param {Number} [options.id] - Ability to add an identifier on the widget (advanced option) * @param {String} [options.apiKey] - API key. The key "calcul" is used by default. * @param {Boolean} [options.ssl = true] - use of ssl or not (default true, service requested using https protocol) * @param {Boolean} [options.collapsed = true] - collapse mode, true by default * @param {Boolean} [options.collapsible = true] - force control to be collapsed or not, true by default. * @param {String} [options.direction = "start"] - TODO : position of picto, by default : "start" * @param {String} [options.placeholder] - Placeholder in search bar. Default is "Rechercher un lieu, une adresse". * @param {Boolean} [options.displayMarker = true] - set a marker on search result, defaults to true. * @param {String} [options.markerStyle = "lightOrange"] - Marker style. Currently possible values are "lightOrange" (default value), "darkOrange", "red" and "turquoiseBlue". * @param {String} [options.markerUrl = ""] - Marker url. By default, if not specified, use option markerStyle. Otherwise, you can added a http url or a base64 image. * @param {Boolean} [options.splitResults = true] - False to disable layers search * @param {Boolean} [options.displayButtonAdvancedSearch = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed) * @param {Boolean} [options.displayButtonGeolocate = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed) * @param {Boolean} [options.displayButtonCoordinateSearch = false] - False to disable advanced search tools (it will not be displayed). Default is false (not displayed) * @param {Boolean} [options.coordinateSearchInAdvancedSearch = false] -True to display coord search in advanced search * @param {Boolean} [options.displayButtonClose = true] - False to disable advanced search tools (it will not be displayed). Default is true (displayed) * @param {Object} [options.coordinateSearch] - coordinates search options. * @param {DOMElement} [options.coordinateSearch.target = null] - TODO : target location of results window. By default under the search bar. * @param {Array} [options.coordinateSearch.units] - list of coordinates units, to be displayed in control units list. * Values may be "DEC" (decimal degrees), "DMS" (sexagecimal) for geographical coordinates, * and "M" or "KM" for metric coordinates * @param {Array} [options.coordinateSearch.systems] - list of projection systems, default are Geographical ("EPSG:4326"), Web Mercator ("EPSG:3857") and Lambert 93 ("EPSG:2154"). * Each array element (=system) is an object with following properties : * @param {String} [options.coordinateSearch.systems.crs] - Proj4 crs alias (from proj4 defs). e.g. : "EPSG:4326". Required * @param {String} [options.coordinateSearch.systems.label] - CRS label to be displayed in control. Default is crs code (e.g. "EPSG:4326") * @param {String} [options.coordinateSearch.systems.type] - CRS units type for coordinates conversion : "Geographical" or "Metric". Default: "Geographical" * @param {Object} [options.advancedSearch] - advanced search options for geocoding (filters). Properties can be found among geocode options.filterOptions (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode}) * @param {DOMElement} [options.advancedSearch.target = null] - TODO : target location of results window. By default under the search bar. * @param {Object} [options.resources] - resources to be used by geocode and autocompletion services : * @param {String} [options.resources.geocode = "location"] - resources geocoding, by default : "location" * @param {Array} [options.resources.autocomplete] - resources autocompletion, by default : ["PositionOfInterest", "StreetAddress"] * @param {Boolean} [options.resources.search = false] - false to disable search service, by default : "false" * @param {Object} [options.searchOptions = {}] - options of search service * @param {Boolean} [options.searchOptions.addToMap = true] - add layer automatically to map, defaults to true. * @param {String[]} [options.searchOptions.filterServices] - filter on a list of search services, each field is separated by a comma. "WMTS,TMS" by default * @param {String[]} [options.searchOptions.filterWMTSPriority] - filter on priority WMTS layer in search, each field is separated by a comma. "PLAN.IGN,ORTHOIMAGERY.ORTHOPHOTOS" by default * @param {String[]} [options.searchOptions.filterProjections] - filter on a list of projections : the searchEngine ignore the suggestions with one of the projections listed. Each field is separated by a comma. * @param {Boolean} [options.searchOptions.filterLayersPriority = false] - filter on priority layers in search, false by default * @param {Boolean} [options.searchOptions.filterLayers] - false to disable the automatic filter from Config or from the filterLayerList parameter. True by Default. * @param {Object} [options.searchOptions.filterLayersList] - filter on list of search layers list with a struture {"layerName" : "service"}. By Default, the layers available in Config.configuration.layers. * @param {Boolean} [options.searchOptions.filterTMS] - filter the results to keep TMS with at least a style (.json) into the metadata. True by Default. * @param {Object} [options.searchOptions.serviceOptions] - options of search service * @param {String} [options.searchOptions.serviceOptions.url] - url of service * @param {String} [options.searchOptions.serviceOptions.index] - index of search, "standard" by default * @param {String[]} [options.searchOptions.serviceOptions.fields] - list of search fields, each field is separated by a comma. "title,layer_name" by default * @param {Number} [options.searchOptions.serviceOptions.size] - number of response in the service. 1000 by default * @param {Number} [options.searchOptions.serviceOptions.maximumResponses] - number of results in the response. 10 by default * @param {Number} [options.searchOptions.maximumEntries] - maximum search results we want to display. * @param {Object} [options.geocodeOptions = {}] - options of geocode service (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~geocode Gp.Services.geocode}) * @param {Object} [options.geocodeOptions.serviceOptions] - options of geocode service * @param {Object} [options.autocompleteOptions = {}] - options of autocomplete service (see {@link http://ignf.github.io/geoportal-access-lib/latest/jsdoc/module-Services.html#~autoComplete Gp.Services.autoComplete}) * @param {Object} [options.autocompleteOptions.serviceOptions] - options of autocomplete service * @param {Boolean} [options.autocompleteOptions.triggerGeocode = false] - trigger a geocoding request if the autocompletion does not return any suggestions, false by default * @param {Number} [options.autocompleteOptions.triggerDelay = 1000] - waiting time before sending the geocoding request, 1000ms by default * @param {Number} [options.autocompleteOptions.maximumEntries] - maximum autocompletion results we want to display * @param {Boolean} [options.autocompleteOptions.prettifyResults = false] - apply a filter/prettifier function to clean or prettify autocomplete entries * @param {Sting|Numeric|Function} [options.zoomTo] - zoom to results, by default, current zoom. * Value possible : auto or zoom level. * Possible to overload it with a function : * zoomTo : function (info) { * // do some stuff... * return zoom; * } * @fires searchengine:autocomplete:click * @fires searchengine:geocode:click * @fires searchengine:search:click * @fires searchengine:geolocation:click * @fires searchengine:geolocation:remove * @fires searchengine:coordinates:click * @todo option : direction (start|end) de la position du picto (loupe) * @todo option : choix du target pour les fenetres geocodage ou recherche par coordonnées * @example * var SearchEngine = ol.control.SearchEngine({ * apiKey : "CLEAPI", * collapsed : true, * collapsible : true, * displayButtonAdvancedSearch : true, * displayButtonGeolocate : true, * displayButtonCoordinateSearch : true, * markerStyle : "lightOrange" // "http://..." or "data/base64..." * resources : { * geocode : ["StreetAddress", "PositionOfInterest"], * autocomplete : ["StreetAddress"], * search : false * }, * advancedSearch : { * target : document.getElementById("dialog"), * PositionOfInterest : [{name : "municipality", title : "Ville"}], * StreetAddress : [{...}] * }, * coordinateSearch : { * target : null * systems : [ * { * "crs" : "EPSG:3857", * "label" : "Web Mercator", * "type" : "Metric" * }, * { * "crs" : "EPSG:4326", * "label" : "Géographiques", * "type" : "Geographical" * } * ], * units : ["DEC", "DMS"] * }, * geocodeOptions : {}, * autocompleteOptions : {}, * searchOptions : {} * }); * * SearchEngine.on("searchengine:autocomplete:click", function (e) { * console.warn("autocomplete", e.location); * }); * SearchEngine.on("searchengine:search:click", function (e) { * console.warn("search", e.suggest); * }); * SearchEngine.on("searchengine:geocode:click", function (e) { * console.warn("geocode", e.location); * }); * SearchEngine.on("searchengine:geolocation:click", function (e) { * console.warn("geolocation", e.); * }); * SearchEngine.on("searchengine:coordinate:click", function (e) { * console.warn("coordinate", e.); * }); */ var SearchEngine = class SearchEngine extends Control { /** * See {@link ol.control.SearchEngine} * @module SearchEngine * @alias module:~controls/SearchEngine * @param {*} options - options * @example * import SearchEngine from "gpf-ext-ol/controls/SearchEngine" * ou * import { SearchEngine } from "gpf-ext-ol" */ constructor (options) { options = options || {}; // call ol.control.Control constructor super(options); if (!(this instanceof SearchEngine)) { throw new TypeError("ERROR CLASS_CONSTRUCTOR"); } /** * Nom de la classe (heritage) * @private */ this.CLASSNAME = "SearchEngine"; // initialisation du composant this.initialize(options); // // Widget main DOM container this.container = this._initContainer(); // ajout du container (this.element) ? this.element.appendChild(this.container) : this.element = this.container; return this; } // ################################################################### // // ##################### public methods ############################## // // ################################################################### // /** * Overwrite OpenLayers setMap method * * @param {ol.Map} map - Map. */ setMap (map) { if (!map) { this._clearResults(); } // mode "collapsed" if (!this.collapsed) { this._showSearchEngineButton.setAttribute("aria-pressed", true); } // on appelle la méthode setMap originale d'OpenLayers super.setMap(map); // position if (this.options.position) { this.setPosition(this.options.position); } // reunion du bouton avec le précédent if (this.options.gutter === false) { this.getContainer().classList.add("gpf-button-no-gutter"); } } /** * Returns true if widget is collapsed (minimized), false otherwise * * @returns {Boolean} collapsed - true if widget is collapsed */ getCollapsed () { return this.collapsed; } /** * Collapse or display widget main container * * @param {Boolean} collapsed - True to collapse widget, False to display it */ setCollapsed (collapsed) { if (collapsed === undefined) { logger.log("[ERROR] SearchEngine:setCollapsed - missing collapsed parameter"); return; } if (!this.options.collapsible) { return; // on interdit le mode pliable ! } if ((collapsed && this.collapsed) || (!collapsed && !this.collapsed)) { return; } this._showSearchEngineButton.click(); this.collapsed = collapsed; } /** * Get locations data from geocode service * * @returns {Object} data - locations */ getData () { return this._geocodedLocations; } /** * Get container * * @returns {DOMElement} container */ getContainer () { return this.container; } // ################################################################### // // ##################### init component ############################## // // ################################################################### // /** * Initialize SearchEngine control (called by SearchEngine constructor) * * @param {Object} options - constructor options * @private */ initialize (options) { this._checkInputOptions(options); // define default options this.options = { collapsed : true, collapsible : true, zoomTo : "", resources : { geocode : [], autocomplete : [], search : false }, displayButtonClose : true, displayButtonAdvancedSearch : false, displayButtonGeolocate : false, displayButtonCoordinateSearch : false, coordinateSearchInAdvancedSearch : false, advancedSearch : {}, coordinateSearch : {}, searchOptions : { addToMap : true, maximumEntries : 5, serviceOptions : { maximumResponses : 10, }, filterLayers : true }, geocodeOptions : { serviceOptions : {} }, autocompleteOptions : { serviceOptions : { maximumResponses : 5, }, triggerGeocode : false, triggerDelay : 1000, prettifyResults : false }, displayMarker : true, markerStyle : "lightOrange", markerUrl : "", placeholder : "Rechercher un lieu, une adresse", splitResults : false, }; // merge with user options Utils.mergeParams(this.options, options); if (this.options.resources.geocode === "") { this.options.resources.geocode = ["PositionOfInterest", "StreetAddress"]; } if (this.options.resources.autocomplete.length === 0) { this.options.resources.autocomplete = ["PositionOfInterest", "StreetAddress"]; } if (this.options.resources.search) { // configuration avec gestion des options surchargées du service if (this.options.searchOptions) { if (this.options.searchOptions.serviceOptions) { if (this.options.searchOptions.serviceOptions.url) { Search.setUrl(this.options.searchOptions.serviceOptions.url); } if (this.options.searchOptions.serviceOptions.fields) { Search.setFields(this.options.searchOptions.serviceOptions.fields); } if (this.options.searchOptions.serviceOptions.index) { Search.setIndex(this.options.searchOptions.serviceOptions.index); } if (this.options.searchOptions.serviceOptions.size) { Search.setSize(this.options.searchOptions.serviceOptions.size); } if (this.options.searchOptions.serviceOptions.maximumResponses) { Search.setMaximumResponses(this.options.searchOptions.serviceOptions.maximumResponses); } } if (this.options.searchOptions.filterServices) { Search.setFiltersByService(this.options.searchOptions.filterServices); } if (this.options.searchOptions.filterLayersPriority) { Search.setFiltersByLayerPriority(this.options.searchOptions.filterLayersPriority); } if (this.options.searchOptions.filterWMTSPriority) { Search.setFilterWMTSPriority(this.options.searchOptions.filterWMTSPriority); } if (this.options.searchOptions.filterTMS === false) { Search.setFilterTMS(this.options.searchOptions.filterTMS); } if (this.options.searchOptions.filterProjections) { Search.setFiltersByProjection(this.options.searchOptions.filterProjections); } } // abonnement au service Search.target.addEventListener("suggest", (e) => { logger.debug(e); let suggestResults = e.detail; // filtre des suggestions selon la configuration ou l'option filterLayersList suggestResults = this._filterResultsFromConfigLayers(suggestResults); this._fillSearchedSuggestListContainer(suggestResults); }); } if (!this.options.collapsible) { this.options.collapsed = false; // on interdit le mode pliable ! } /** {Boolean} specify if searchEngine control is collapsed (true) or not (false) */ this.collapsed = this.options.collapsed; // identifiant du contrôle : utile pour suffixer les identifiants CSS (pour gérer le cas où il y en a plusieurs dans la même page) this._uid = this.options.id || SelectorID.generate(); this._showSearchEngineButton = null; this._showSearchEngineAdvancedButton = null; // container de l'input de recherche this._inputSearchContainer = null; // container des reponses de l'autocompletion / du service de recherche this._autocompleteContainer = null; this._containerResultsLocation = null; this._containerResultsSuggest = null; // Radio buttons correspondants this._radioButtonLocation = null; this._radioButtonSuggest = null; // listes des reponses de l'autocompletion this._suggestedLocations = []; // container des reponses du geocodage this._geocodedContainer = null; // liste des reponses du geocodage this._geocodedLocations = []; // container des filtres du geocodage this._filterContainer = null; // ressource de geocodage selectionnée pour le geocodage avancé this._currentGeocodingCode = null; // localisant this._currentGeocodingLocation = null; // liste des filtres du geocodage pour le geocodage avancé this._advancedSearchFilters = {}; this._initAdvancedSearchFilters(); // liste des ressources du geocodage pour le geocodage avancé this._advancedSearchCodes = []; this._initAdvancedSearchCodes(); // recherche par coordonnées : systemes de projections this._coordinateSearchSystems = []; if (this.options.displayButtonCoordinateSearch) { this._initCoordinateSearchSystems(); this._currentCoordinateSearchSystems = this._coordinateSearchSystems[0]; // epsg:4326 this._currentCoordinateSearchType = this._coordinateSearchSystems[0].type; // geographical ou metric } // recherche par coordonnées : unités this._coordinateSearchUnits = []; if (this.options.displayButtonCoordinateSearch) { this._initCoordinateSearchUnits(); this._currentCoordinateSearchUnits = this._coordinateSearchUnits[this._currentCoordinateSearchType][0].code; // decimal } this._coordinateSearchLngInput = null; this._coordinateSearchLatInput = null; // marker this._marker = null; // marker style or url var _markerStyle = this.options.markerStyle; var _markerUrl = this.options.markerUrl; if (_markerUrl) { this._markerUrl = _markerUrl; } else { this._markerUrl = (Object.keys(Markers).indexOf(_markerStyle) === -1) ? Markers["lightOrange"] : Markers[_markerStyle]; } // marker display this._displayMarker = this.options.displayMarker; // popup this._popupContent = null; this._popupDiv = this._initPopupDiv(); this._popupOverlay = null; // trigger geocode this._triggerHandler = null; } /** * this method is called by this.initialize() * and makes sure input options are correctly formated * * @param {Object} options - options * * @private */ _checkInputOptions (options) { var i; if (options.resources) { // on vérifie que resources est bien un objet if (typeof options.resources === "object") { // ressources de geocodage var geocodeResources = options.resources.geocode; if (geocodeResources) { // on vérifie que la liste des ressources de geocodage est bien un tableau if (Array.isArray(geocodeResources)) { var geocodeResourcesList = ["StreetAddress", "PositionOfInterest", "CadastralParcel", "Administratif"]; for (i = 0; i < geocodeResources.length; i++) { if (geocodeResourcesList.indexOf(geocodeResources[i]) === -1) { // si la resource n'est pas référencée, on l'enlève // geocodeResources.splice(i, 1); logger.log("[SearchEngine] options.resources.geocode : " + geocodeResources[i] + " is not a resource for geocode"); } } } else { logger.log("[SearchEngine] 'options.resources.geocode' parameter should be an array"); geocodeResources = null; } } // ressources d'autocompletion var autocompleteResources = options.resources.autocomplete; if (autocompleteResources) { // on vérifie que la liste des ressources d'autocompletion est bien un tableau if (Array.isArray(autocompleteResources)) { var autocompleteResourcesList = ["StreetAddress", "PositionOfInterest"]; for (i = 0; i < autocompleteResources.length; i++) { if (autocompleteResourcesList.indexOf(autocompleteResources[i]) === -1) { // si la resource n'est pas référencée, on l'enlève // autocompleteResources.splice(i, 1); logger.log("[SearchEngine] options.resources.autocomplete : " + autocompleteResources[i] + " is not a resource for autocomplete"); } } } else { logger.log("[SearchEngine] 'options.resources.autocomplete' parameter should be an array"); autocompleteResources = null; } } } else { logger.log("[SearchEngine] 'resources' parameter should be an object"); options.resources = null; } } } /** * this method is called by this.initialize() * and initialize the geocoding resources titles. * * @private */ _initAdvancedSearchCodes () { // INFORMATION // on y ajoute les filtres attributaires pour une table de ressources // selectionnée via un evenement (onchange) de la liste deroulante du // menu avancé du geocodage. // cf. onGeocodingAdvancedSearchCodeChange() pour la selection de la // ressource de geocodage à afficher var geocodeResources = this.options.resources.geocode; if (geocodeResources === "location") { geocodeResources = ["PositionOfInterest", "StreetAddress", "CadastralParcel"]; } if (!Array.isArray(geocodeResources)) { geocodeResources = [geocodeResources]; } for (var i = 0; i < geocodeResources.length; i++) { switch (geocodeResources[i]) { case "PositionOfInterest": this._advancedSearchCodes.push({ id : "PositionOfInterest", title : "Lieux/toponymes" }); break; case "StreetAddress": this._advancedSearchCodes.push({ id : "StreetAddress", title : "Adresses" }); break; case "CadastralParcel": this._advancedSearchCodes.push({ id : "CadastralParcel", title : "Parcelles cadastrales" }); break; default: break; } } // par défaut, au cas où aucune ressource passée en option ne correspond à celles attendues if (this._advancedSearchCodes.length === 0) { this._advancedSearchCodes = [{ id : "StreetAddress", title : "Adresses" }, { id : "PositionOfInterest", title : "Lieux/toponymes" }, { id : "CadastralParcel", title : "Cadastre" }]; } logger.log("advancedSearchCodes", this._advancedSearchCodes); } /** * this method is called by this.onAdd() * and initialize the advanced geocoding filters. * * @private */ _initAdvancedSearchFilters () { // liste des filtres par defauts pour toutes les ressources this._advancedSearchFilters = SearchEngineUtils.advancedSearchFiltersByDefault; // on merge les options avancées avec celles par defaut var advancedSearchFiltersCustom = this.options.advancedSearch; Utils.assign(this._advancedSearchFilters, advancedSearchFiltersCustom); logger.log("advancedSearchFilters", this._advancedSearchFilters); } /** * this method is called by the constructor and initialize the projection * systems. * getting coordinates in the requested projection : * see this.onCoordinateSearchSystemChange() * * @private */ _initCoordinateSearchSystems () { // on donne la possibilité à l'utilisateur de modifier // la liste des systèmes à afficher // Ex. this.options.coordinateSearch.systems // systemes de projection disponible par defaut var projectionSystemsByDefault = [{ label : "G\u00e9ographique", crs : "EPSG:4326", type : "Geographical" }, { label : "Web Mercator", crs : "EPSG:3857", type : "Metric" }, { label : "Lambert 93", crs : "EPSG:2154", type : "Metric" }]; var systems = this.options.coordinateSearch.systems; if (systems) { // on ajoute les definitions d'un systeme de reference fournies par l'utilisateur for (var i = 0; i < systems.length; i++) { var sys = systems[i]; this._setSystem(sys); } } // on ajoute les systèmes de projections par défaut if (this._coordinateSearchSystems.length === 0) { for (var j = 0; j < projectionSystemsByDefault.length; j++) { this._setSystem(projectionSystemsByDefault[j]); } } } /** * this method is called by the constructor and initialize the units. * getting coordinates in the requested units : * see this.onCoordinateSearchUnitsChange() * * @private */ _initCoordinateSearchUnits () { // on donne la possibilité à l'utilisateur de modifier // la liste des unités à afficher // Ex. // this.options.units : ["DEC", "DMS"] // unités disponible par defaut var projectionUnitsByDefault = { Geographical : [{ code : "DEC", label : "degrés décimaux", format : MathUtils.coordinateToDecimal }, { code : "DMS", label : "degrés sexagésimaux", format : MathUtils.coordinateToDMS }], Metric : [{ code : "M", label : "mètres", format : MathUtils.coordinateToMeter }, { code : "KM", label : "kilomètres", format : MathUtils.coordinateToKMeter }] }; var units = this.options.coordinateSearch.units; if (units) { for (var type in projectionUnitsByDefault) { if (projectionUnitsByDefault.hasOwnProperty(type)) { var found = false; for (var j = 0; j < projectionUnitsByDefault[type].length; j++) { var obj = projectionUnitsByDefault[type][j]; for (var i = 0; i < units.length; i++) { var unit = units[i]; if (obj.code === unit) { found = true; if (!this._coordinateSearchUnits[type]) { this._coordinateSearchUnits[type] = []; } this._coordinateSearchUnits[type].push(obj); } } } if (!found) { this._coordinateSearchUnits[type] = projectionUnitsByDefault[type]; } } } } // au cas où... if (typeof this._coordinateSearchUnits === "object" && Object.keys(this._coordinateSearchUnits).length === 0) { this._coordinateSearchUnits = projectionUnitsByDefault; } } /** * this method is called by this.initialize() and initialize popup div * (to display results information on marker click) * * @return {Object} element - DOM element for popup * @private */ _initPopupDiv () { var context = this; var element = document.createElement("div"); element.className = "gp-feature-info-div gpf-widget-color"; // bouton de suppression de la pop-up / marker // var span = document.createElement("span"); // span.className = "GPelementHidden gpf-visible"; // afficher en dsfr // span.innerText = "Supprimer"; var remove = document.createElement("button"); remove.title = "Supprimer le marqueur"; remove.className = "gp-styling-button remove gpf-btn gpf-btn-icon-remove fr-btn--remove fr-btn fr-btn--tertiary-no-outline fr-mt-1v fr-mr-2v"; // on remove click : remove marker remove.onclick = function () { var map = context.getMap(); if (context._marker) { map.removeOverlay(context._marker); context._marker = null; } if (context._popupOverlay != null) { context._popupOverlay.setPosition(undefined); } /** * event triggered when i want a remove geolocation popup * * @event searchengine:geolocation:remove * @property {Object} type - event * @property {Object} target - instance SearchEngine * @example * SearchEngine.on("searchengine:geolocation:remove", function (e) { * console.log(e.coordinates); * }) */ context.dispatchEvent({ type : "searchengine:geolocation:remove" }); }; // remove.appendChild(span); // bouton de fermeture de la pop-up var closer = document.createElement("button"); closer.title = "Fermer la pop-up"; closer.className = "gp-styling-button closer gpf-btn gpf-btn-icon-close fr-btn--close fr-btn fr-btn--tertiary-no-outline fr-mt-1v fr-mr-2v"; // on closer click : remove popup closer.onclick = function () { if (context._popupOverlay != null) { context._popupOverlay.setPosition(undefined); } return false; }; this._popupContent = document.createElement("div"); this._popupContent.className = "gp-features-content-div"; this._popupContent.style["min-width"] = "200px"; element.appendChild(closer); element.appendChild(this._popupContent); element.appendChild(remove); return element; } // ################################################################### // // ######################## DOM initialize ########################### // // ################################################################### // /** * Create control main container * * @returns {DOMElement} DOM element * * @private */ _initContainer () { // create main container var container = this._createMainContainerElement(); var searchDiv = this._createSearchDivElement(); // create search engine picto var picto = this._showSearchEngineButton = this._createShowSearchEnginePictoElement(this.options.collapsible); searchDiv.appendChild(picto); // only dsfr : on applique un fond blanc sur une barre de recherche fixe if (!this.options.collapsible) { container.classList.add("gpf-widget-color", "gpf-widget-padding"); } var search = this._inputSearchContainer = this._createSearchInputElement(this.options.placeholder); if (this.options.displayButtonClose) { search.appendChild(this._createSearchResetElement()); } var context = this; if (search.addEventListener) { search.addEventListener("click", function () { context.onAutoCompleteInputClick(); }); } else if (search.attachEvent) { search.attachEvent("onclick", function () { context.onAutoCompleteInputClick(); }); } searchDiv.appendChild(search); var buttonsContainer = this._createButtonsElement(); var firstLineWrapper = this._createFirstLineWrapper(); firstLineWrapper.appendChild(searchDiv); firstLineWrapper.appendChild(buttonsContainer); container.appendChild(firstLineWrapper); if (checkDsfr() && this.options.splitResults) { var radioContainer = this._createRadioContainer(); container.appendChild(radioContainer); } if (checkDsfr() && this.options.splitResults) { var radioElements; [radioElements, this._radioButtonLocation, this._radioButtonSuggest] = this._createRadioElements(); radioContainer.appendChild(radioElements); } if (this.options.displayButtonGeolocate) { var geolocateShow = this._createShowGeolocateElement(); buttonsContainer.appendChild(geolocateShow); } if (this.options.displayButtonCoordinateSearch || this.options.coordinateSearchInAdvancedSearch) { var searchByCoordinateShow = this._createShowSearchByCoordinateElement(); if (!this.options.coordinateSearchInAdvancedSearch) { buttonsContainer.appendChild(searchByCoordinateShow); } var coordinatePanel = this._createCoordinateSearchPanelElement(); var coordinatePanelDiv = this._createCoordinateSearchPanelDivElement(); var coordinateHeader = this._createCoordinateSearchPanelHeaderElement(); var coordinateForm = this._createCoordinateSearchPanelFormElement(); var div = null; div = this._containerSystems = this.__createCoordinateSearchDivElement(); coordinateForm.appendChild(div); var labelSystems = this._createCoordinateSearchSystemsLabelElement(); var systems = this._setCoordinateSearchSystemsSelectElement(this._coordinateSearchSystems); div.appendChild(labelSystems); div.appendChild(systems); div = this._containerUnits = this.__createCoordinateSearchDivElement(); coordinateForm.appendChild(div); var labelUnits = this._createCoordinateSearchUnitsLabelElement(); var units = this._setCoordinateSearchUnitsSelectElement(this._coordinateSearchUnits[this._currentCoordinateSearchType]); div.appendChild(labelUnits); div.appendChild(units); div = this._containerCoordinateLng = this.__createCoordinateSearchDivElement(); coordinateForm.appendChild(div); var coordinateLng = this._setCoordinateSearchLngLabelElement(this._currentCoordinateSearchType); var coordinateInputLng = this._coordinateSearchLngInput = this._setCoordinateSearchLngInputElement(this._currentCoordinateSearchUnits); div.appendChild(coordinateLng); div.appendChild(coordinateInputLng); div = this._containerCoordinateLat = this.__createCoordinateSearchDivElement(); coordinateForm.appendChild(div); var coordinateLat = this._setCoordinateSearchLatLabelElement(this._currentCoordinateSearchType); var coordinateInputLat = this._coordinateSearchLatInput = this._setCoordinateSearchLatInputElement(this._currentCoordinateSearchUnits); div.appendChild(coordinateLat); div.appendChild(coordinateInputLat); var submit = this._createCoordinateSearchSubmitElement(); coordinateForm.appendChild(submit); coordinatePanelDiv.appendChild(coordinateHeader); coordinatePanelDiv.appendChild(coordinateForm); coordinatePanel.appendChild(coordinatePanelDiv); if (!this.options.coordinateSearchInAdvancedSearch) { container.appendChild(coordinatePanel); } } if (this.options.displayButtonAdvancedSearch) { var advancedShow = this._showSearchEngineAdvancedButton = this._createShowAdvancedSearchElement(); buttonsContainer.appendChild(advancedShow); // INFO je decompose les appels car j'ai besoin de recuperer le container // des filtres var advancedPanel = this._createAdvancedSearchPanelElement(); var advancedPanelDiv = this._createAdvancedSearchPanelDivElement(); var advancedHeader = this._createAdvancedSearchPanelHeaderElement(); var advancedForm = this._createAdvancedSearchPanelFormElement(this._advancedSearchCodes, this.options.coordinateSearchInAdvancedSearch); var advancedFormFilters = this._filterContainer = this._createAdvancedSearchFormFiltersElement(); this._setFilter(this._advancedSearchCodes[0].id); // ex "PositionOfInterest" var advancedFormInput = this._createAdvancedSearchFormInputElement(); advancedForm.appendChild(advancedFormFilters); if (this.options.coordinateSearchInAdvancedSearch) { advancedForm.appendChild(coordinateForm); } advancedForm.appendChild(advancedFormInput); advancedPanelDiv.appendChild(advancedHeader); advancedPanelDiv.appendChild(advancedForm); advancedPanel.appendChild(advancedPanelDiv); container.appendChild(advancedPanel); } // INFO je decompose les appels car j'ai besoin de recuperer le container // des resultats de l'autocompletion var autocomplete = this._autocompleteContainer = this._createAutoCompleteElement(); var autocompleteList = this._createAutoCompleteListElement(); var containerResultsLocation = this._containerResultsLocation = this._createAutoCompletedLocationContainer(); var containerResultsSuggest = this._containerResultsSuggest = this._createSearchedSuggestContainer(); autocompleteList.appendChild(containerResultsLocation); autocompleteList.appendChild(containerResultsSuggest); autocomplete.appendChild(autocompleteList); container.appendChild(autocomplete); // INFO je decompose les appels car j'ai besoin de recuperer le container // des resultats du geocodage var geocode = this._createGeocodeResultsElement(); var geocodeDiv = this._createGeocodeResultsDivElement(); geocode.appendChild(geocodeDiv); var geocodeList = this._geocodedContainer = this._createGeocodeResultsListElement(); geocodeDiv.appendChild(geocodeList); container.appendChild(geocode); return container; } /** * this method is called by : * - this._initContainer() : ... * - this.onGeocodingAdvancedSearchCodeChoice() : ... * and initialize or create the filters container HTMLElement * to the geocoding advanced menu. * * @param {String} code - resource geocoding name * * @returns {DOMElement} DOM element * @private */ _setFilter (code) { // INFORMATION // Nous avons 2 solutions possibles pour la mise en place des filtres. // 1. Soit on decide de creer tous les filtres pour chaque ressource // de geocodage à l'initialisation du composant, et on joue sur le // mode 'hidden' pour n'afficher que la ressource selectionnée. // 2. Soit on decide de creer à chaque fois les filtres pour la // ressource selectionnée. // Chaque solution a ses inconvenients/avantages. // Implementation du choix 2 car elle offre plus de souplesse pour // recuperer les 'form-data'... var container = this._filterContainer; var codeFound = false; for (var i = 0; i < this._advancedSearchCodes.length; i++) { if (this._advancedSearchCodes[i].id === code) { codeFound = true; break; } } if (!codeFound) { // cette ressource n'est pas disponible, // on supprime les anciens enfants... while (container.firstChild) { container.removeChild(container.firstChild); } return; } // on sauvegarde la ressource de geocodage sélectionnée this._currentGeocodingCode = code; // on supprime les enfants... while (container.firstChild) { container.removeChild(container.firstChild); } var lstAttributs = this._advancedSearchFilters[code]; if (!lstAttributs || lstAttributs.length === 0) { // cette ressource n'est pas parametrable return; } var divTable = this._createAdvancedSearchFiltersTableElement(code, true); for (var j = 0; j < lstAttributs.length; j++) { var divFilter = this._createAdvancedSearchFiltersAttributElement(lstAttributs[j]); divTable.appendChild(divFilter); } container.appendChild(divTable); return container; } // ################################################################### // // ################ methods to request and results ################### // // ################################################################### // /** * this method is called by this.onAutoCompleteSearch() * and executes a request to the service. * * @param {Object} settings - service settings * @param {String} settings.text - text * @param {Function} settings.onSuccess - callback * @param {Function} settings.onFailure - callback * @private */ _requestAutoComplete (settings) { // on ne fait pas de requête si on n'a pas renseigné de parametres ! if (!settings || (typeof settings === "object" && Object.keys(settings).length === 0)) { return; } // on ne fait pas de requête si la parametre 'text' est vide ! if (!settings.text) { return; } logger.log(settings); var options = {}; // on recupere les options du service Utils.assign(options, this.options.autocompleteOptions.serviceOptions); // ainsi que la recherche et les callbacks Utils.assign(options, settings); // on ajoute le paramètre filterOptions.type spécifiant les ressources. var resources = this.options.resources.autocomplete; if (resources && Array.isArray(resources)) { // il se peut que l'utilisateur ait surchargé ce paramètre dans geocodeOptions, if (!options.type) { options.type = resources; } } // cas où la clef API n'est pas renseignée dans les options du service, // on utilise celle renseignée au niveau du controle ou la clé "calcul" par défaut. options.apiKey = options.apiKey || this.options.apiKey; // si l'utilisateur a spécifié le paramètre ssl au niveau du control, on s'en sert // true par défaut (https) if (typeof options.ssl !== "boolean") { if (typeof this.options.ssl === "boolean") { options.ssl = this.options.ssl; } else { options.ssl = true; } } logger.log(options); Gp.Services.autoComplete(options); } /** * this method is called by this.onAutoCompleteSearchText() (case of success) * and fills the container of the location list. * it creates a HTML Element per location * * @param {Array} locations - Array of Gp.Services.AutoComplete.SuggestedLocation corresponding to autocomplete results list * @private */ _fillAutoCompletedLocationListContainer (locations) { if (!locations || locations.length === 0) { return; } // on vide la liste avant de la construire var element = this._containerResultsLocation; if (element.childElementCount) { while (element.firstChild) { element.removeChild(element.firstChild); } } element.classList.add("GPelementHidden", "gpf-hidden"); if (locations.length) { if (!this._radioButtonLocation || (this._radioButtonLocation && this._radioButtonLocation.checked)) { element.classList.remove("GPelementHidden", "gpf-hidden"); } this._displaySuggestedLocation(); if (!checkDsfr() || !this.options.splitResults) { this._createAutoCompletedLocationTitleElement(); } for (var i = 0; i < locations.length; i++) { // Proposals are dynamically filled in Javascript by autocomplete service this