From 55cc7a098f7f9ff29c084c90388c631c9b24ff8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexis=20M=C3=A9taireau?= Date: Tue, 12 Mar 2024 09:39:22 +0100 Subject: [PATCH] refactor: Separate ui rendering from data updates `U.SCHEMA` now contains an `impacts` key, which makes it possible to specify what part of the UI is impacted by data changes. A new `render` method has been added on `U.Map` and `U.DataLayer`, which is used to rerender the proper parts of the UI depending on the passed properties. `U.FormBuilder` calls this `render()` method (if present), during form changes. --- umap/static/umap/js/modules/schema.js | 567 ++++++++++++++------------ umap/static/umap/js/modules/utils.js | 27 +- umap/static/umap/js/umap.forms.js | 5 +- umap/static/umap/js/umap.js | 101 +++-- umap/static/umap/js/umap.layer.js | 46 ++- 5 files changed, 416 insertions(+), 330 deletions(-) diff --git a/umap/static/umap/js/modules/schema.js b/umap/static/umap/js/modules/schema.js index d34f8ffc..f0bd3f2e 100644 --- a/umap/static/umap/js/modules/schema.js +++ b/umap/static/umap/js/modules/schema.js @@ -1,45 +1,52 @@ import { translate } from './i18n.js' +// Possible impacts +// ['ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data', 'background'] + export const SCHEMA = { - zoom: { - type: Number, - }, - scrollWheelZoom: { + browsable: { type: Boolean, - label: translate('Allow scroll wheel zoom?'), + impacts: ['ui'], }, - scaleControl: { + captionBar: { + impacts: ['ui'], type: Boolean, - label: translate('Do you want to display the scale control?'), + label: translate('Do you want to display a caption bar?'), + default: false, + impacts: [], + }, + captionMenus: { + impacts: ['ui'], + type: Boolean, + label: translate('Do you want to display caption menus?'), default: true, }, - moreControl: { - type: Boolean, - label: translate('Do you want to display the «more» control?'), - default: true, - }, - miniMap: { - type: Boolean, - label: translate('Do you want to display a minimap?'), - default: false, - }, - displayPopupFooter: { - type: Boolean, - label: translate('Do you want to display popup footer?'), - default: false, - }, - onLoadPanel: { + color: { + impacts: ['data'], type: String, - label: translate('Do you want to display a panel on load?'), - choices: [ - ['none', translate('None')], - ['caption', translate('Caption')], - ['databrowser', translate('Data browser')], - ['facet', translate('Facet search')], - ], - default: 'none', + handler: 'ColorPicker', + label: translate('color'), + helpEntries: 'colorValue', + inheritable: true, + default: 'DarkBlue', + }, + dashArray: { + impacts: ['data'], + type: String, + label: translate('dash array'), + helpEntries: 'dashArray', + inheritable: true, + }, + datalayersControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + handler: 'DataLayersControl', + label: translate('Display the data layers control'), + default: true, }, defaultView: { + impacts: [], // no need to update the ui, only useful when loading the map type: String, label: translate('Default view'), choices: [ @@ -50,37 +57,80 @@ export const SCHEMA = { ], default: 'center', }, - name: { - type: String, - label: translate('name'), - }, description: { label: translate('description'), + impacts: ['ui'], type: 'Text', helpEntries: 'textFormatting', }, - licence: { - type: String, - label: translate('licence'), + displayOnLoad: { + type: Boolean, + impacts: [], }, - tilelayer: { - type: Object, + displayPopupFooter: { + type: Boolean, + impacts: ['ui'], + label: translate('Do you want to display popup footer?'), + default: false, }, - overlay: { - type: Object, + easing: { impacts: [], type: Boolean, default: false }, + editinosmControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the control to open OpenStreetMap editor'), + default: null, }, - limitBounds: { - type: Object, + embedControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the embed control'), + default: true, }, - color: { + facetKey: { impacts: ['ui'], type: String }, + fill: { + impacts: ['data'], + type: Boolean, + label: translate('fill'), + helpEntries: 'fill', + inheritable: true, + default: true, + }, + fillColor: { + impacts: ['data'], type: String, handler: 'ColorPicker', - label: translate('color'), - helpEntries: 'colorValue', + label: translate('fill color'), + helpEntries: 'fillColor', inheritable: true, - default: 'DarkBlue', + }, + fillOpacity: { + impacts: ['data'], + type: Number, + min: 0.1, + max: 1, + step: 0.1, + label: translate('fill opacity'), + inheritable: true, + default: 0.3, + }, + filterKey: { impacts: [], type: String }, + fromZoom: { + impacts: [], // not needed + type: Number, + label: translate('From zoom'), + helpText: translate('Optional.'), + }, + fullscreenControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the fullscreen control'), + default: true, }, iconClass: { + impacts: ['data'], type: String, label: translate('Icon shape'), inheritable: true, @@ -92,26 +142,8 @@ export const SCHEMA = { ], default: 'Default', }, - iconUrl: { - type: String, - handler: 'IconUrl', - label: translate('Icon symbol'), - inheritable: true, - // helpText: translate( - // 'Symbol can be either a unicode character or an URL. You can use feature properties as variables: ex.: with "http://myserver.org/images/{name}.png", the {name} variable will be replaced by the "name" value of each marker.' - // ), - }, - smoothFactor: { - type: Number, - min: 0, - max: 10, - step: 0.5, - label: translate('Simplify'), - helpEntries: 'smoothFactor', - inheritable: true, - default: 1.0, - }, iconOpacity: { + impacts: ['data'], type: Number, min: 0.1, max: 1, @@ -120,132 +152,28 @@ export const SCHEMA = { inheritable: true, default: 1, }, - opacity: { - type: Number, - min: 0.1, - max: 1, - step: 0.1, - label: translate('opacity'), + iconUrl: { + impacts: ['data'], + type: String, + handler: 'IconUrl', + label: translate('Icon symbol'), inheritable: true, - default: 0.5, }, - weight: { - type: Number, - min: 1, - max: 20, - step: 1, - label: translate('weight'), - inheritable: true, - default: 3, - }, - fill: { + inCaption: { type: Boolean, - label: translate('fill'), - helpEntries: 'fill', + impacts: ['ui'], + }, + + interactive: { + impacts: ['data'], + type: Boolean, + label: translate('Allow interactions'), + helpEntries: 'interactive', inheritable: true, default: true, }, - fillColor: { - type: String, - handler: 'ColorPicker', - label: translate('fill color'), - helpEntries: 'fillColor', - inheritable: true, - }, - fillOpacity: { - type: Number, - min: 0.1, - max: 1, - step: 0.1, - label: translate('fill opacity'), - inheritable: true, - default: 0.3, - }, - dashArray: { - type: String, - label: translate('dash array'), - helpEntries: 'dashArray', - inheritable: true, - }, - popupShape: { - type: String, - label: translate('Popup shape'), - inheritable: true, - choices: [ - ['Default', translate('Popup')], - ['Large', translate('Popup (large)')], - ['Panel', translate('Side panel')], - ], - default: 'Default', - }, - popupTemplate: { - type: String, - label: translate('Popup content style'), - inheritable: true, - choices: [ - ['Default', translate('Default')], - ['Table', translate('Table')], - ['GeoRSSImage', translate('GeoRSS (title + image)')], - ['GeoRSSLink', translate('GeoRSS (only link)')], - ['OSM', translate('OpenStreetMap')], - ], - default: 'Default', - }, - popupContentTemplate: { - type: 'Text', - label: translate('Popup content template'), - helpEntries: ['dynamicProperties', 'textFormatting'], - placeholder: '# {name}', - inheritable: true, - default: '# {name}\n{description}', - }, - zoomTo: { - type: Number, - placeholder: translate('Inherit'), - helpEntries: 'zoomTo', - label: translate('Default zoom level'), - inheritable: true, - }, - captionBar: { - type: Boolean, - label: translate('Do you want to display a caption bar?'), - default: false, - }, - captionMenus: { - type: Boolean, - label: translate('Do you want to display caption menus?'), - default: true, - }, - slideshow: { - type: Object, - }, - sortKey: { - type: String, - }, - labelKey: { - type: String, - helpEntries: 'labelKey', - placeholder: translate('Default: name'), - label: translate('Label key'), - inheritable: true, - }, - filterKey: { - type: String, - }, - facetKey: { - type: String, - }, - slugKey: { - type: String, - }, - showLabel: { - type: Boolean, - nullable: true, - label: translate('Display label'), - inheritable: true, - default: false, - }, labelDirection: { + impacts: ['data'], type: String, label: translate('Label direction'), inheritable: true, @@ -259,11 +187,87 @@ export const SCHEMA = { default: 'auto', }, labelInteractive: { + impacts: ['data'], type: Boolean, label: translate('Labels are clickable'), inheritable: true, }, + labelKey: { + impacts: ['data'], + type: String, + helpEntries: 'labelKey', + placeholder: translate('Default: name'), + label: translate('Label key'), + inheritable: true, + }, + licence: { impacts: ['ui'], type: String, label: translate('licence') }, + limitBounds: { impacts: ['limit-bounds'], type: Object }, + locateControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the locate control'), + }, + longCredit: { + impacts: ['ui'], + type: 'Text', + label: translate('Long credits'), + helpEntries: ['longCredit', 'textFormatting'], + }, + measureControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the measure control'), + }, + miniMap: { + impacts: ['ui'], + type: Boolean, + label: translate('Do you want to display a minimap?'), + default: false, + }, + moreControl: { + impacts: ['ui'], + type: Boolean, + label: translate('Do you want to display the «more» control?'), + default: true, + }, + name: { + impacts: ['ui', 'data'], + type: String, + label: translate('name'), + }, + onLoadPanel: { + impacts: [], // This is what happens during the map instantiation + type: String, + label: translate('Do you want to display a panel on load?'), + choices: [ + ['none', translate('None')], + ['caption', translate('Caption')], + ['databrowser', translate('Data browser')], + ['facet', translate('Facet search')], + ], + default: 'none', + }, + opacity: { + impacts: ['data'], + type: Number, + min: 0.1, + max: 1, + step: 0.1, + label: translate('opacity'), + inheritable: true, + default: 0.5, + }, + outlink: { + type: String, + label: translate('Link to…'), + helpEntries: 'outlink', + placeholder: 'http://...', + inheritable: true, + }, outlinkTarget: { + impacts: [], type: String, label: translate('Open link in…'), inheritable: true, @@ -274,115 +278,162 @@ export const SCHEMA = { ['parent', translate('parent window')], ], }, - shortCredit: { - type: String, - label: translate('Short credits'), - helpEntries: ['shortCredit', 'textFormatting'], - }, - longCredit: { - type: 'Text', - label: translate('Long credits'), - helpEntries: ['longCredit', 'textFormatting'], - }, + overlay: { impacts: ['background'], type: Object }, permanentCredit: { + impacts: ['ui'], type: 'Text', label: translate('Permanent credits'), helpEntries: ['permanentCredit', 'textFormatting'], }, permanentCreditBackground: { + impacts: ['ui'], type: Boolean, label: translate('Permanent credits background'), default: true, }, - zoomControl: { + popupContentTemplate: { + impacts: [], // not needed + type: 'Text', + label: translate('Popup content template'), + helpEntries: ['dynamicProperties', 'textFormatting'], + placeholder: '# {name}', + inheritable: true, + default: '# {name}\n{description}', + }, + popupShape: { + impacts: [], // not needed + type: String, + label: translate('Popup shape'), + inheritable: true, + choices: [ + ['Default', translate('Popup')], + ['Large', translate('Popup (large)')], + ['Panel', translate('Side panel')], + ], + default: 'Default', + }, + popupTemplate: { + impacts: [], // not needed + type: String, + label: translate('Popup content style'), + inheritable: true, + choices: [ + ['Default', translate('Default')], + ['Table', translate('Table')], + ['GeoRSSImage', translate('GeoRSS (title + image)')], + ['GeoRSSLink', translate('GeoRSS (only link)')], + ['OSM', translate('OpenStreetMap')], + ], + default: 'Default', + }, + remoteData: { + type: Object, + impacts: ['remote-data'], + }, + scaleControl: { + impacts: ['ui'], type: Boolean, - nullable: true, - label: translate('Display the zoom control'), + label: translate('Do you want to display the scale control?'), default: true, }, - datalayersControl: { + scrollWheelZoom: { + impacts: ['ui'], type: Boolean, - nullable: true, - handler: 'DataLayersControl', - label: translate('Display the data layers control'), - default: true, + label: translate('Allow scroll wheel zoom?'), }, searchControl: { + impacts: ['ui'], type: Boolean, nullable: true, label: translate('Display the search control'), default: true, }, - locateControl: { + shortCredit: { + impacts: ['ui'], + type: String, + label: translate('Short credits'), + helpEntries: ['shortCredit', 'textFormatting'], + }, + showLabel: { + impacts: ['data'], type: Boolean, nullable: true, - label: translate('Display the locate control'), + label: translate('Display label'), + inheritable: true, + default: false, }, - fullscreenControl: { - type: Boolean, - nullable: true, - label: translate('Display the fullscreen control'), - default: true, - }, - editinosmControl: { - type: Boolean, - nullable: true, - label: translate('Display the control to open OpenStreetMap editor'), - default: null, - }, - embedControl: { - type: Boolean, - nullable: true, - label: translate('Display the embed control'), - default: true, - }, - measureControl: { - type: Boolean, - nullable: true, - label: translate('Display the measure control'), - }, - tilelayersControl: { - type: Boolean, - nullable: true, - label: translate('Display the tile layers control'), + slideshow: { impacts: ['ui'], type: Object }, + slugKey: { impacts: [], type: String }, + smoothFactor: { + impacts: ['data'], + type: Number, + min: 0, + max: 10, + step: 0.5, + label: translate('Simplify'), + helpEntries: 'smoothFactor', + inheritable: true, + default: 1.0, }, + sortKey: { impacts: ['data', 'datalayer-index'], type: String }, starControl: { + impacts: ['ui'], type: Boolean, nullable: true, label: translate('Display the star map button'), }, - easing: { - type: Boolean, - default: false, - }, - interactive: { - type: Boolean, - label: translate('Allow interactions'), - helpEntries: 'interactive', - inheritable: true, - default: true, - }, - fromZoom: { - type: Number, - label: translate('From zoom'), - helpText: translate('Optional.'), - }, - toZoom: { - type: Number, - label: translate('To zoom'), - helpText: translate('Optional.'), - }, stroke: { + impacts: ['data'], type: Boolean, label: translate('stroke'), helpEntries: 'stroke', inheritable: true, default: true, }, - outlink: { - label: translate('Link to…'), - helpEntries: 'outlink', - placeholder: 'http://...', + tilelayer: { impacts: ['background'], type: Object }, + tilelayersControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the tile layers control'), + }, + toZoom: { + type: Number, + impacts: [], // not needed + label: translate('To zoom'), + helpText: translate('Optional.'), + }, + type: { + type: 'String', + impacts: ['data'], + }, + weight: { + impacts: ['data'], + type: Number, + min: 1, + max: 20, + step: 1, + label: translate('weight'), + inheritable: true, + default: 3, + }, + zoom: { + impacts: [], // default zoom, doesn't need to be updated + type: Number, + }, + zoomControl: { + impacts: ['ui'], + type: Boolean, + nullable: true, + label: translate('Display the zoom control'), + default: true, + }, + zoomTo: { + impacts: [], // not need to update the view + type: Number, + placeholder: translate('Inherit'), + helpEntries: 'zoomTo', + label: translate('Default zoom level'), inheritable: true, }, } diff --git a/umap/static/umap/js/modules/utils.js b/umap/static/umap/js/modules/utils.js index e6117c04..82819ed0 100644 --- a/umap/static/umap/js/modules/utils.js +++ b/umap/static/umap/js/modules/utils.js @@ -25,6 +25,31 @@ export function checkId(string) { return /^[A-Za-z0-9]{5}$/.test(string) } +/** + * Compute the impacts for a given list of fields. + * + * Return a set containing the impacts. + * + * @param {fields} list[fields] + * @returns Set[string] + */ +export function getImpactsFromSchema(fields) { + return fields + .map((field) => { + // remove the option prefix for fields + // And only keep the first part in case of a subfield + // (e.g "options.limitBounds.foobar" will just return "limitBounds") + return field.replace('options.', '').split('.')[0] + }) + .reduce((acc, field) => { + // retrieve the "impacts" field from the schema + // and merge them together using sets + const impacts = U.SCHEMA[field]?.impacts || [] + impacts.forEach((impact) => acc.add(impact)) + return acc + }, new Set()) +} + /** * Import DOM purify, and initialize it. * @@ -303,5 +328,5 @@ export function template(str, data) { value = value(data) } return value - }) + } } diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index 865da9f5..f1e41077 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -1032,7 +1032,10 @@ U.FormBuilder = L.FormBuilder.extend({ setter: function (field, value) { L.FormBuilder.prototype.setter.call(this, field, value) - if (this.options.makeDirty !== false) this.obj.isDirty = true + if (this.options.makeDirty !== false) { + this.obj.isDirty = true + if ('render' in this.obj) this.obj.render([field], this) + } }, finish: function () { diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 1fae103f..bbcf9502 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -69,6 +69,7 @@ U.Map = L.Map.extend({ this.options.zoomControl = zoomControl !== undefined ? zoomControl : true this.options.fullscreenControl = fullscreenControl !== undefined ? fullscreenControl : true + this.datalayersFromQueryString = L.Util.queryString('datalayers') if (this.datalayersFromQueryString) { this.datalayersFromQueryString = this.datalayersFromQueryString @@ -242,6 +243,42 @@ U.Map = L.Map.extend({ this.on('click contextmenu.show', this.closeInplaceToolbar) }, + render: function (fields) { + let impacts = U.Utils.getImpactsFromSchema(fields) + + for (let impact of impacts) { + switch (impact) { + case 'ui': + this.initCaptionBar() + this.renderEditToolbar() + this.renderControls() + break + case 'data': + this.redrawVisibleDataLayers() + break + case 'datalayer-index': + this.reindexDataLayers() + break + case 'background': + this.initTileLayers() + break + case 'bounds': + this.handleLimitBounds() + break + } + } + }, + + reindexDataLayers: function () { + this.eachDataLayer((datalayer) => datalayer.reindex()) + }, + + redrawVisibleDataLayers: function () { + this.eachVisibleDataLayer((datalayer) => { + datalayer.redraw() + }) + }, + setOptionsFromQueryString: function (options) { // This is not an editable option L.Util.setFromQueryString(options, 'editMode') @@ -269,6 +306,8 @@ U.Map = L.Map.extend({ } }, + // Merge the given schema with the default one + // Missing keys inside the schema are merged with the default ones. overrideSchema: function (schema) { for (const [key, extra] of Object.entries(schema)) { U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra) @@ -1128,13 +1167,7 @@ U.Map = L.Map.extend({ 'options.captionBar', 'options.captionMenus', ]) - builder = new U.FormBuilder(this, UIFields, { - callback: function () { - this.renderControls() - this.initCaptionBar() - }, - callbackContext: this, - }) + builder = new U.FormBuilder(this, UIFields) const controlsOptions = L.DomUtil.createFieldset( container, L._('User interface options') @@ -1157,14 +1190,7 @@ U.Map = L.Map.extend({ 'options.dashArray', ] - builder = new U.FormBuilder(this, shapeOptions, { - callback: function (e) { - if (this._controls.miniMap) this.renderControls() - this.eachVisibleDataLayer((datalayer) => { - datalayer.redraw() - }) - }, - }) + builder = new U.FormBuilder(this, shapeOptions) const defaultShapeProperties = L.DomUtil.createFieldset( container, L._('Default shape properties') @@ -1217,14 +1243,7 @@ U.Map = L.Map.extend({ ], ] - builder = new U.FormBuilder(this, optionsFields, { - callback: function (e) { - this.initCaptionBar() - if (e.helper.field === 'options.sortKey') { - this.eachDataLayer((datalayer) => datalayer.reindex()) - } - }, - }) + builder = new U.FormBuilder(this, optionsFields) const defaultProperties = L.DomUtil.createFieldset( container, L._('Default properties') @@ -1242,20 +1261,7 @@ U.Map = L.Map.extend({ 'options.labelInteractive', 'options.outlinkTarget', ] - builder = new U.FormBuilder(this, popupFields, { - callback: function (e) { - if ( - e.helper.field === 'options.popupTemplate' || - e.helper.field === 'options.popupContentTemplate' || - e.helper.field === 'options.popupShape' || - e.helper.field === 'options.outlinkTarget' - ) - return - this.eachVisibleDataLayer((datalayer) => { - datalayer.redraw() - }) - }, - }) + builder = new U.FormBuilder(this, popupFields) const popupFieldset = L.DomUtil.createFieldset( container, L._('Default interaction options') @@ -1309,10 +1315,7 @@ U.Map = L.Map.extend({ container, L._('Custom background') ) - builder = new U.FormBuilder(this, tilelayerFields, { - callback: this.initTileLayers, - callbackContext: this, - }) + builder = new U.FormBuilder(this, tilelayerFields) customTilelayer.appendChild(builder.build()) }, @@ -1360,10 +1363,7 @@ U.Map = L.Map.extend({ ['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }], ] const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay')) - builder = new U.FormBuilder(this, overlayFields, { - callback: this.initTileLayers, - callbackContext: this, - }) + builder = new U.FormBuilder(this, overlayFields) overlay.appendChild(builder.build()) }, @@ -1390,10 +1390,7 @@ U.Map = L.Map.extend({ { handler: 'BlurFloatInput', placeholder: L._('max East') }, ], ] - const boundsBuilder = new U.FormBuilder(this, boundsFields, { - callback: this.handleLimitBounds, - callbackContext: this, - }) + const boundsBuilder = new U.FormBuilder(this, boundsFields) limitBounds.appendChild(boundsBuilder.build()) const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds) L.DomUtil.createButton( @@ -1454,7 +1451,6 @@ U.Map = L.Map.extend({ ] const slideshowHandler = function () { this.slideshow.setOptions(this.options.slideshow) - this.renderControls() } const slideshowBuilder = new U.FormBuilder(this, slideshowFields, { callback: slideshowHandler, @@ -1472,10 +1468,7 @@ U.Map = L.Map.extend({ 'options.permanentCredit', 'options.permanentCreditBackground', ] - const creditsBuilder = new U.FormBuilder(this, creditsFields, { - callback: this.renderControls, - callbackContext: this, - }) + const creditsBuilder = new U.FormBuilder(this, creditsFields) credits.appendChild(creditsBuilder.build()) }, diff --git a/umap/static/umap/js/umap.layer.js b/umap/static/umap/js/umap.layer.js index 0279691a..02c92d40 100644 --- a/umap/static/umap/js/umap.layer.js +++ b/umap/static/umap/js/umap.layer.js @@ -254,16 +254,18 @@ U.Layer.Choropleth = L.FeatureGroup.extend({ }, onEdit: function (field, builder) { + // Only compute the breaks if we're dealing with choropleth + if (!field.startsWith('options.choropleth')) return // If user touches the breaks, then force manual mode if (field === 'options.choropleth.breaks') { this.datalayer.options.choropleth.mode = 'manual' - builder.helpers['options.choropleth.mode'].fetch() + if (builder) builder.helpers['options.choropleth.mode'].fetch() } this.computeBreaks() // If user changes the mode or the number of classes, // then update the breaks input value if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') { - builder.helpers['options.choropleth.breaks'].fetch() + if (builder) builder.helpers['options.choropleth.breaks'].fetch() } }, @@ -594,6 +596,31 @@ U.DataLayer = L.Evented.extend({ if (this.autoLoaded()) this.map.on('zoomend', this.onZoomEnd, this) }, + render: function (fields, builder) { + let impacts = U.Utils.getImpactsFromSchema(fields) + + for (let impact of impacts) { + switch (impact) { + case 'ui': + this.map.updateDatalayersControl() + break + case 'data': + if (fields.includes('options.type')) { + this.resetLayer() + } + this.hide() + fields.forEach((field) => { + this.layer.onEdit(field, builder) + }) + this.show() + break + case 'remote-data': + this.fetchRemoteData() + break + } + } + }, + onMoveEnd: function (e) { if (this.isRemoteLayer() && this.showAtZoom()) this.fetchRemoteData() }, @@ -1189,29 +1216,18 @@ U.DataLayer = L.Evented.extend({ const title = L.DomUtil.add('h3', '', container, L._('Layer properties')) let builder = new U.FormBuilder(this, metadataFields, { callback: function (e) { - this.map.updateDatalayersControl() if (e.helper.field === 'options.type') { - this.resetLayer() this.edit() } }, }) container.appendChild(builder.build()) - const redrawCallback = function (e) { - const field = e.helper.field, - builder = e.helper.builder - this.hide() - this.layer.onEdit(field, builder) - this.show() - } - const layerOptions = this.layer.getEditableOptions() if (layerOptions.length) { builder = new U.FormBuilder(this, layerOptions, { id: 'datalayer-layer-properties', - callback: redrawCallback, }) const layerProperties = L.DomUtil.createFieldset( container, @@ -1235,7 +1251,6 @@ U.DataLayer = L.Evented.extend({ builder = new U.FormBuilder(this, shapeOptions, { id: 'datalayer-advanced-properties', - callback: redrawCallback, }) const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties')) shapeProperties.appendChild(builder.build()) @@ -1251,7 +1266,6 @@ U.DataLayer = L.Evented.extend({ builder = new U.FormBuilder(this, optionsFields, { id: 'datalayer-advanced-properties', - callback: redrawCallback, }) const advancedProperties = L.DomUtil.createFieldset( container, @@ -1269,7 +1283,7 @@ U.DataLayer = L.Evented.extend({ 'options.outlinkTarget', 'options.interactive', ] - builder = new U.FormBuilder(this, popupFields, { callback: redrawCallback }) + builder = new U.FormBuilder(this, popupFields) const popupFieldset = L.DomUtil.createFieldset( container, L._('Interaction options')