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.
This commit is contained in:
Alexis Métaireau 2024-03-12 09:39:22 +01:00
parent 99a0007ce0
commit 55cc7a098f
5 changed files with 416 additions and 330 deletions

View file

@ -1,45 +1,52 @@
import { translate } from './i18n.js' import { translate } from './i18n.js'
// Possible impacts
// ['ui', 'data', 'limit-bounds', 'datalayer-index', 'remote-data', 'background']
export const SCHEMA = { export const SCHEMA = {
zoom: { browsable: {
type: Number,
},
scrollWheelZoom: {
type: Boolean, type: Boolean,
label: translate('Allow scroll wheel zoom?'), impacts: ['ui'],
}, },
scaleControl: { captionBar: {
impacts: ['ui'],
type: Boolean, 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, default: true,
}, },
moreControl: { color: {
type: Boolean, impacts: ['data'],
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: {
type: String, type: String,
label: translate('Do you want to display a panel on load?'), handler: 'ColorPicker',
choices: [ label: translate('color'),
['none', translate('None')], helpEntries: 'colorValue',
['caption', translate('Caption')], inheritable: true,
['databrowser', translate('Data browser')], default: 'DarkBlue',
['facet', translate('Facet search')], },
], dashArray: {
default: 'none', 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: { defaultView: {
impacts: [], // no need to update the ui, only useful when loading the map
type: String, type: String,
label: translate('Default view'), label: translate('Default view'),
choices: [ choices: [
@ -50,37 +57,80 @@ export const SCHEMA = {
], ],
default: 'center', default: 'center',
}, },
name: {
type: String,
label: translate('name'),
},
description: { description: {
label: translate('description'), label: translate('description'),
impacts: ['ui'],
type: 'Text', type: 'Text',
helpEntries: 'textFormatting', helpEntries: 'textFormatting',
}, },
licence: { displayOnLoad: {
type: String, type: Boolean,
label: translate('licence'), impacts: [],
}, },
tilelayer: { displayPopupFooter: {
type: Object, type: Boolean,
impacts: ['ui'],
label: translate('Do you want to display popup footer?'),
default: false,
}, },
overlay: { easing: { impacts: [], type: Boolean, default: false },
type: Object, editinosmControl: {
impacts: ['ui'],
type: Boolean,
nullable: true,
label: translate('Display the control to open OpenStreetMap editor'),
default: null,
}, },
limitBounds: { embedControl: {
type: Object, 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, type: String,
handler: 'ColorPicker', handler: 'ColorPicker',
label: translate('color'), label: translate('fill color'),
helpEntries: 'colorValue', helpEntries: 'fillColor',
inheritable: true, 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: { iconClass: {
impacts: ['data'],
type: String, type: String,
label: translate('Icon shape'), label: translate('Icon shape'),
inheritable: true, inheritable: true,
@ -92,26 +142,8 @@ export const SCHEMA = {
], ],
default: 'Default', 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: { iconOpacity: {
impacts: ['data'],
type: Number, type: Number,
min: 0.1, min: 0.1,
max: 1, max: 1,
@ -120,132 +152,28 @@ export const SCHEMA = {
inheritable: true, inheritable: true,
default: 1, default: 1,
}, },
opacity: { iconUrl: {
type: Number, impacts: ['data'],
min: 0.1, type: String,
max: 1, handler: 'IconUrl',
step: 0.1, label: translate('Icon symbol'),
label: translate('opacity'),
inheritable: true, inheritable: true,
default: 0.5,
}, },
weight: { inCaption: {
type: Number,
min: 1,
max: 20,
step: 1,
label: translate('weight'),
inheritable: true,
default: 3,
},
fill: {
type: Boolean, type: Boolean,
label: translate('fill'), impacts: ['ui'],
helpEntries: 'fill', },
interactive: {
impacts: ['data'],
type: Boolean,
label: translate('Allow interactions'),
helpEntries: 'interactive',
inheritable: true, inheritable: true,
default: 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: { labelDirection: {
impacts: ['data'],
type: String, type: String,
label: translate('Label direction'), label: translate('Label direction'),
inheritable: true, inheritable: true,
@ -259,11 +187,87 @@ export const SCHEMA = {
default: 'auto', default: 'auto',
}, },
labelInteractive: { labelInteractive: {
impacts: ['data'],
type: Boolean, type: Boolean,
label: translate('Labels are clickable'), label: translate('Labels are clickable'),
inheritable: true, 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: { outlinkTarget: {
impacts: [],
type: String, type: String,
label: translate('Open link in…'), label: translate('Open link in…'),
inheritable: true, inheritable: true,
@ -274,115 +278,162 @@ export const SCHEMA = {
['parent', translate('parent window')], ['parent', translate('parent window')],
], ],
}, },
shortCredit: { overlay: { impacts: ['background'], type: Object },
type: String,
label: translate('Short credits'),
helpEntries: ['shortCredit', 'textFormatting'],
},
longCredit: {
type: 'Text',
label: translate('Long credits'),
helpEntries: ['longCredit', 'textFormatting'],
},
permanentCredit: { permanentCredit: {
impacts: ['ui'],
type: 'Text', type: 'Text',
label: translate('Permanent credits'), label: translate('Permanent credits'),
helpEntries: ['permanentCredit', 'textFormatting'], helpEntries: ['permanentCredit', 'textFormatting'],
}, },
permanentCreditBackground: { permanentCreditBackground: {
impacts: ['ui'],
type: Boolean, type: Boolean,
label: translate('Permanent credits background'), label: translate('Permanent credits background'),
default: true, 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, type: Boolean,
nullable: true, label: translate('Do you want to display the scale control?'),
label: translate('Display the zoom control'),
default: true, default: true,
}, },
datalayersControl: { scrollWheelZoom: {
impacts: ['ui'],
type: Boolean, type: Boolean,
nullable: true, label: translate('Allow scroll wheel zoom?'),
handler: 'DataLayersControl',
label: translate('Display the data layers control'),
default: true,
}, },
searchControl: { searchControl: {
impacts: ['ui'],
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the search control'), label: translate('Display the search control'),
default: true, default: true,
}, },
locateControl: { shortCredit: {
impacts: ['ui'],
type: String,
label: translate('Short credits'),
helpEntries: ['shortCredit', 'textFormatting'],
},
showLabel: {
impacts: ['data'],
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the locate control'), label: translate('Display label'),
inheritable: true,
default: false,
}, },
fullscreenControl: { slideshow: { impacts: ['ui'], type: Object },
type: Boolean, slugKey: { impacts: [], type: String },
nullable: true, smoothFactor: {
label: translate('Display the fullscreen control'), impacts: ['data'],
default: true, type: Number,
}, min: 0,
editinosmControl: { max: 10,
type: Boolean, step: 0.5,
nullable: true, label: translate('Simplify'),
label: translate('Display the control to open OpenStreetMap editor'), helpEntries: 'smoothFactor',
default: null, inheritable: true,
}, default: 1.0,
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'),
}, },
sortKey: { impacts: ['data', 'datalayer-index'], type: String },
starControl: { starControl: {
impacts: ['ui'],
type: Boolean, type: Boolean,
nullable: true, nullable: true,
label: translate('Display the star map button'), 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: { stroke: {
impacts: ['data'],
type: Boolean, type: Boolean,
label: translate('stroke'), label: translate('stroke'),
helpEntries: 'stroke', helpEntries: 'stroke',
inheritable: true, inheritable: true,
default: true, default: true,
}, },
outlink: { tilelayer: { impacts: ['background'], type: Object },
label: translate('Link to…'), tilelayersControl: {
helpEntries: 'outlink', impacts: ['ui'],
placeholder: 'http://...', 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, inheritable: true,
}, },
} }

View file

@ -25,6 +25,31 @@ export function checkId(string) {
return /^[A-Za-z0-9]{5}$/.test(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. * Import DOM purify, and initialize it.
* *
@ -303,5 +328,5 @@ export function template(str, data) {
value = value(data) value = value(data)
} }
return value return value
}) }
} }

View file

@ -1032,7 +1032,10 @@ U.FormBuilder = L.FormBuilder.extend({
setter: function (field, value) { setter: function (field, value) {
L.FormBuilder.prototype.setter.call(this, 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 () { finish: function () {

View file

@ -69,6 +69,7 @@ U.Map = L.Map.extend({
this.options.zoomControl = zoomControl !== undefined ? zoomControl : true this.options.zoomControl = zoomControl !== undefined ? zoomControl : true
this.options.fullscreenControl = this.options.fullscreenControl =
fullscreenControl !== undefined ? fullscreenControl : true fullscreenControl !== undefined ? fullscreenControl : true
this.datalayersFromQueryString = L.Util.queryString('datalayers') this.datalayersFromQueryString = L.Util.queryString('datalayers')
if (this.datalayersFromQueryString) { if (this.datalayersFromQueryString) {
this.datalayersFromQueryString = this.datalayersFromQueryString this.datalayersFromQueryString = this.datalayersFromQueryString
@ -242,6 +243,42 @@ U.Map = L.Map.extend({
this.on('click contextmenu.show', this.closeInplaceToolbar) 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) { setOptionsFromQueryString: function (options) {
// This is not an editable option // This is not an editable option
L.Util.setFromQueryString(options, 'editMode') 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) { overrideSchema: function (schema) {
for (const [key, extra] of Object.entries(schema)) { for (const [key, extra] of Object.entries(schema)) {
U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra) U.SCHEMA[key] = L.extend({}, U.SCHEMA[key], extra)
@ -1128,13 +1167,7 @@ U.Map = L.Map.extend({
'options.captionBar', 'options.captionBar',
'options.captionMenus', 'options.captionMenus',
]) ])
builder = new U.FormBuilder(this, UIFields, { builder = new U.FormBuilder(this, UIFields)
callback: function () {
this.renderControls()
this.initCaptionBar()
},
callbackContext: this,
})
const controlsOptions = L.DomUtil.createFieldset( const controlsOptions = L.DomUtil.createFieldset(
container, container,
L._('User interface options') L._('User interface options')
@ -1157,14 +1190,7 @@ U.Map = L.Map.extend({
'options.dashArray', 'options.dashArray',
] ]
builder = new U.FormBuilder(this, shapeOptions, { builder = new U.FormBuilder(this, shapeOptions)
callback: function (e) {
if (this._controls.miniMap) this.renderControls()
this.eachVisibleDataLayer((datalayer) => {
datalayer.redraw()
})
},
})
const defaultShapeProperties = L.DomUtil.createFieldset( const defaultShapeProperties = L.DomUtil.createFieldset(
container, container,
L._('Default shape properties') L._('Default shape properties')
@ -1217,14 +1243,7 @@ U.Map = L.Map.extend({
], ],
] ]
builder = new U.FormBuilder(this, optionsFields, { builder = new U.FormBuilder(this, optionsFields)
callback: function (e) {
this.initCaptionBar()
if (e.helper.field === 'options.sortKey') {
this.eachDataLayer((datalayer) => datalayer.reindex())
}
},
})
const defaultProperties = L.DomUtil.createFieldset( const defaultProperties = L.DomUtil.createFieldset(
container, container,
L._('Default properties') L._('Default properties')
@ -1242,20 +1261,7 @@ U.Map = L.Map.extend({
'options.labelInteractive', 'options.labelInteractive',
'options.outlinkTarget', 'options.outlinkTarget',
] ]
builder = new U.FormBuilder(this, popupFields, { 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()
})
},
})
const popupFieldset = L.DomUtil.createFieldset( const popupFieldset = L.DomUtil.createFieldset(
container, container,
L._('Default interaction options') L._('Default interaction options')
@ -1309,10 +1315,7 @@ U.Map = L.Map.extend({
container, container,
L._('Custom background') L._('Custom background')
) )
builder = new U.FormBuilder(this, tilelayerFields, { builder = new U.FormBuilder(this, tilelayerFields)
callback: this.initTileLayers,
callbackContext: this,
})
customTilelayer.appendChild(builder.build()) customTilelayer.appendChild(builder.build())
}, },
@ -1360,10 +1363,7 @@ U.Map = L.Map.extend({
['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }], ['options.overlay.tms', { handler: 'Switch', label: L._('TMS format') }],
] ]
const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay')) const overlay = L.DomUtil.createFieldset(container, L._('Custom overlay'))
builder = new U.FormBuilder(this, overlayFields, { builder = new U.FormBuilder(this, overlayFields)
callback: this.initTileLayers,
callbackContext: this,
})
overlay.appendChild(builder.build()) overlay.appendChild(builder.build())
}, },
@ -1390,10 +1390,7 @@ U.Map = L.Map.extend({
{ handler: 'BlurFloatInput', placeholder: L._('max East') }, { handler: 'BlurFloatInput', placeholder: L._('max East') },
], ],
] ]
const boundsBuilder = new U.FormBuilder(this, boundsFields, { const boundsBuilder = new U.FormBuilder(this, boundsFields)
callback: this.handleLimitBounds,
callbackContext: this,
})
limitBounds.appendChild(boundsBuilder.build()) limitBounds.appendChild(boundsBuilder.build())
const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds) const boundsButtons = L.DomUtil.create('div', 'button-bar half', limitBounds)
L.DomUtil.createButton( L.DomUtil.createButton(
@ -1454,7 +1451,6 @@ U.Map = L.Map.extend({
] ]
const slideshowHandler = function () { const slideshowHandler = function () {
this.slideshow.setOptions(this.options.slideshow) this.slideshow.setOptions(this.options.slideshow)
this.renderControls()
} }
const slideshowBuilder = new U.FormBuilder(this, slideshowFields, { const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
callback: slideshowHandler, callback: slideshowHandler,
@ -1472,10 +1468,7 @@ U.Map = L.Map.extend({
'options.permanentCredit', 'options.permanentCredit',
'options.permanentCreditBackground', 'options.permanentCreditBackground',
] ]
const creditsBuilder = new U.FormBuilder(this, creditsFields, { const creditsBuilder = new U.FormBuilder(this, creditsFields)
callback: this.renderControls,
callbackContext: this,
})
credits.appendChild(creditsBuilder.build()) credits.appendChild(creditsBuilder.build())
}, },

View file

@ -254,16 +254,18 @@ U.Layer.Choropleth = L.FeatureGroup.extend({
}, },
onEdit: function (field, builder) { 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 user touches the breaks, then force manual mode
if (field === 'options.choropleth.breaks') { if (field === 'options.choropleth.breaks') {
this.datalayer.options.choropleth.mode = 'manual' this.datalayer.options.choropleth.mode = 'manual'
builder.helpers['options.choropleth.mode'].fetch() if (builder) builder.helpers['options.choropleth.mode'].fetch()
} }
this.computeBreaks() this.computeBreaks()
// If user changes the mode or the number of classes, // If user changes the mode or the number of classes,
// then update the breaks input value // then update the breaks input value
if (field === 'options.choropleth.mode' || field === 'options.choropleth.classes') { 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) 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) { onMoveEnd: function (e) {
if (this.isRemoteLayer() && this.showAtZoom()) this.fetchRemoteData() 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')) const title = L.DomUtil.add('h3', '', container, L._('Layer properties'))
let builder = new U.FormBuilder(this, metadataFields, { let builder = new U.FormBuilder(this, metadataFields, {
callback: function (e) { callback: function (e) {
this.map.updateDatalayersControl()
if (e.helper.field === 'options.type') { if (e.helper.field === 'options.type') {
this.resetLayer()
this.edit() this.edit()
} }
}, },
}) })
container.appendChild(builder.build()) 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() const layerOptions = this.layer.getEditableOptions()
if (layerOptions.length) { if (layerOptions.length) {
builder = new U.FormBuilder(this, layerOptions, { builder = new U.FormBuilder(this, layerOptions, {
id: 'datalayer-layer-properties', id: 'datalayer-layer-properties',
callback: redrawCallback,
}) })
const layerProperties = L.DomUtil.createFieldset( const layerProperties = L.DomUtil.createFieldset(
container, container,
@ -1235,7 +1251,6 @@ U.DataLayer = L.Evented.extend({
builder = new U.FormBuilder(this, shapeOptions, { builder = new U.FormBuilder(this, shapeOptions, {
id: 'datalayer-advanced-properties', id: 'datalayer-advanced-properties',
callback: redrawCallback,
}) })
const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties')) const shapeProperties = L.DomUtil.createFieldset(container, L._('Shape properties'))
shapeProperties.appendChild(builder.build()) shapeProperties.appendChild(builder.build())
@ -1251,7 +1266,6 @@ U.DataLayer = L.Evented.extend({
builder = new U.FormBuilder(this, optionsFields, { builder = new U.FormBuilder(this, optionsFields, {
id: 'datalayer-advanced-properties', id: 'datalayer-advanced-properties',
callback: redrawCallback,
}) })
const advancedProperties = L.DomUtil.createFieldset( const advancedProperties = L.DomUtil.createFieldset(
container, container,
@ -1269,7 +1283,7 @@ U.DataLayer = L.Evented.extend({
'options.outlinkTarget', 'options.outlinkTarget',
'options.interactive', 'options.interactive',
] ]
builder = new U.FormBuilder(this, popupFields, { callback: redrawCallback }) builder = new U.FormBuilder(this, popupFields)
const popupFieldset = L.DomUtil.createFieldset( const popupFieldset = L.DomUtil.createFieldset(
container, container,
L._('Interaction options') L._('Interaction options')