nakarte

Source code of https://map.sikmir.ru (fork)
git clone git://git.sikmir.ru/nakarte
Log | Files | Refs | LICENSE

commit c28a1ef57a405645ed00fe37d2a30e79b0a71b35
parent 6261adefa4f41c71580f43f6596f9dd61b9b4166
Author: Sergej Orlov <wladimirych@gmail.com>
Date:   Fri,  5 Dec 2025 16:49:51 +0100

leaflet.control.layers.configure: fix checkbox state after "Cancel" clicked

The problem was caused by fact, that layers were updated while dialog
window was not added to DOM.
Fixed by refactoring:
- moved layers config code to separate class
- dialog window is not removed from DOM, visibility is controlled by
  "visible" binding
- observable attributes "checked" are not  added to layers anymore,
  instead we create separate model for the dialog

Fixes: #1367

Diffstat:
Msrc/lib/leaflet.control.layers.configure/index.js | 234+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
Msrc/lib/leaflet.control.layers.configure/style.css | 16++++++++--------
2 files changed, 148 insertions(+), 102 deletions(-)

diff --git a/src/lib/leaflet.control.layers.configure/index.js b/src/lib/leaflet.control.layers.configure/index.js @@ -7,6 +7,126 @@ import * as logging from '~/lib/logging'; import safeLocalStorage from '~/lib/safe-localstorage'; import './customLayer'; +class LayersConfigDialog { + constructor(builtInLayers, customLayers, cbOk) { + this.builtInLayers = builtInLayers; + this.customLayers = customLayers; + this.cbOk = cbOk; + + this.visible = ko.observable(false); + this.layerGroups = ko.observableArray([]); + + this.initWindow(); + } + + initWindow() { + const container = this.window = + L.DomUtil.create('div', 'leaflet-layers-dialog-wrapper'); + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); + container.setAttribute('data-bind', "visible: visible"); + container.innerHTML = ` +<div class="leaflet-layers-config-window"> + <form> + <!-- ko foreach: layerGroups --> + <div class="section-header" data-bind="html: group"></div> + <!-- ko foreach: layers --> + <label> + <input type="checkbox" data-bind="checked: enabled"/> + <span data-bind="text: title"> + </span> + </label> + <!-- /ko --> + <!-- /ko --> + </form> + <div class="buttons-row"> + <div href="#" class="button" data-bind="click: onOkClicked">Ok</div> + <div href="#" class="button" data-bind="click: onCancelClicked">Cancel</div> + <div href="#" class="button" data-bind="click: onResetClicked">Reset</div> + </div> +</div> + `; + ko.applyBindings(this, container); + } + + getWindow() { + return this.window; + } + + showDialog() { + this.updateModelFromLayers(); + this.visible(true); + } + + updateModelFromLayers() { + this.layerGroups.removeAll(); + for (const group of this.builtInLayers) { + this.layerGroups.push({ + group: group.group, + layers: ko.observableArray( + group.layers.map((l) => ({ + title: l.title, + enabled: ko.observable(l.enabled), + origLayer: l, + })) + ), + }); + } + if (this.customLayers.length) { + this.layerGroups.push({ + group: 'Custom layers', + layers: ko.observableArray( + this.customLayers.map((l) => ({ + title: l.title, + enabled: ko.observable(l.enabled), + origLayer: l, + })) + ), + }); + } + } + + updateLayersFromModel() { + for (const group of this.layerGroups()) { + for (const layer of group.layers()) { + layer.origLayer.enabled = layer.enabled(); + } + } + } + + getLayersEnabledOnlyInModel() { + const newLayers = []; + for (const group of this.layerGroups()) { + for (const layer of group.layers()) { + if (layer.enabled() && !layer.origLayer.enabled) { + newLayers.push(layer.origLayer); + } + } + } + return newLayers; + } + + onOkClicked() { + const newEnabledLayers = this.getLayersEnabledOnlyInModel(); + this.updateLayersFromModel(); + this.visible(false); + this.cbOk(newEnabledLayers); + } + + onCancelClicked() { + this.visible(false); + } + + onResetClicked() { + for (const group of this.layerGroups()) { + for (const layer of group.layers()) { + layer.enabled(layer.origLayer.isDefault); + } + } + } +} + function enableConfig(control, {layers, customLayersOrder}) { if (control._configEnabled) { return; @@ -20,13 +140,14 @@ function enableConfig(control, {layers, customLayersOrder}) { L.Util.extend(control, { _configEnabled: true, - _allLayersGroups: layers, - _allLayers: [].concat(...layers.map((group) => group.layers)), - _customLayers: ko.observableArray(), + _builtinLayersByGroup: layers, + _builtinLayers: [].concat(...layers.map((group) => group.layers)), + _customLayers: [], onAdd: function(map) { const container = originalOnAdd.call(this, map); this.__injectConfigButton(); + this.initLayersConfigWindow(); this.loadSettings(); return container; }, @@ -110,105 +231,31 @@ function enableConfig(control, {layers, customLayersOrder}) { layersSettingsByCode[it.code] = it; }); - for (let layer of [...this._allLayers, ...this._customLayers()]) { + for (let layer of [...this._builtinLayers, ...this._customLayers]) { const layerSettings = layersSettingsByCode[layer.layer.options.code] ?? {}; // if storage is empty enable only default layers // if new default layer appears it will be enabled - let enabled = layerSettings.enabled ?? layer.isDefault; - layer.enabled = enabled; - layer.checked = ko.observable(enabled); + layer.enabled = layerSettings.enabled ?? layer.isDefault; } this.updateLayers(); }, _onConfigButtonClick: function() { - this.showLayersSelectWindow(); - }, - - _initLayersSelectWindow: function() { - if (this._configWindow) { - return; - } - - const container = this._configWindow = - L.DomUtil.create('div', 'leaflet-layers-dialog-wrapper'); - L.DomEvent - .disableClickPropagation(container) - .disableScrollPropagation(container); - container.innerHTML = ` -<div class="leaflet-layers-select-window"> - <form> - <!-- ko foreach: _allLayersGroups --> - <div class="section-header" data-bind="html: group"></div> - <!-- ko foreach: layers --> - <label> - <input type="checkbox" data-bind="checked: checked"/> - <span data-bind="text: title"> - </span> - </label> - <!-- /ko --> - <!-- /ko --> - <div data-bind="if: _customLayers().length" class="section-header">Custom layers</div> - <!-- ko foreach: _customLayers --> - <label> - <input type="checkbox" data-bind="checked: checked"/> - <span data-bind="text: title"></span> - </label> - <!-- /ko --> - </form> - <div class="buttons-row"> - <div href="#" class="button" data-bind="click: onSelectWindowOkClicked">Ok</div> - <div href="#" class="button" data-bind="click: onSelectWindowCancelClicked">Cancel</div> - <div href="#" class="button" data-bind="click: onSelectWindowResetClicked">Reset</div> - </div> -</div> - `; - ko.applyBindings(this, container); - }, - - showLayersSelectWindow: function() { - if (this._configWindowVisible || this._customLayerWindow) { - return; - } - [...this._allLayers, ...this._customLayers()].forEach((layer) => layer.checked(layer.enabled)); - this._initLayersSelectWindow(); - this._map._controlContainer.appendChild(this._configWindow); - this._configWindowVisible = true; - }, - - hideSelectWindow: function() { - if (!this._configWindowVisible) { + if (this._layersConfigDialog.visible() || this._customLayerWindow) { return; } - this._map._controlContainer.removeChild(this._configWindow); - this._configWindowVisible = false; + this._layersConfigDialog.showDialog(); }, - onSelectWindowCancelClicked: function() { - this.hideSelectWindow(); - }, - - onSelectWindowResetClicked: function() { - if (!this._configWindow) { - return; - } - [...this._allLayers, ...this._customLayers()].forEach((layer) => layer.checked(layer.isDefault)); + initLayersConfigWindow: function() { + this._layersConfigDialog = new LayersConfigDialog( + this._builtinLayersByGroup, this._customLayers, this.onConfigDialogOkClicked.bind(this) + ); + this._map._controlContainer.appendChild(this._layersConfigDialog.getWindow()); }, - onSelectWindowOkClicked: function() { - const newEnabledLayers = []; - for (let layer of [...this._allLayers, ...this._customLayers()]) { - if (layer.checked()) { - if (!layer.enabled) { - newEnabledLayers.push(layer); - } - layer.enabled = true; - } else { - layer.enabled = false; - } - } - this.updateLayers(newEnabledLayers); - this.hideSelectWindow(); + onConfigDialogOkClicked: function(addedLayers) { + this.updateLayers(addedLayers); }, onCustomLayerCreateClicked: function() { @@ -236,12 +283,12 @@ function enableConfig(control, {layers, customLayersOrder}) { }, updateLayersListControl: function(addedLayers) { - const disabledLayers = [...this._allLayers, ...this._customLayers()].filter((l) => !l.enabled); + const disabledLayers = [...this._builtinLayers, ...this._customLayers].filter((l) => !l.enabled); disabledLayers.forEach((l) => this._map.removeLayer(l.layer)); [...this._layers].forEach((l) => this.removeLayer(l.layer)); let hasBaselayerOnMap = false; - const enabledLayers = [...this._allLayers, ...this._customLayers()].filter((l) => l.enabled); + const enabledLayers = [...this._builtinLayers, ...this._customLayers].filter((l) => l.enabled); enabledLayers.sort((l1, l2) => l1.order - l2.order); enabledLayers.forEach((l) => { l.layer._justAdded = addedLayers && addedLayers.includes(l); @@ -275,7 +322,7 @@ function enableConfig(control, {layers, customLayersOrder}) { saveSettings: function() { const layersSettings = []; - for (let layer of [...this._allLayers, ...this._customLayers()]) { + for (let layer of [...this._builtinLayers, ...this._customLayers]) { layersSettings.push({ code: layer.layer.options.code, isCustom: layer.isCustom, @@ -292,7 +339,7 @@ function enableConfig(control, {layers, customLayersOrder}) { let newCode = this.loadCustomLayerFromString(code); return newCode || code; }); - for (let layer of [...this._allLayers, ...this._customLayers()]) { + for (let layer of [...this._builtinLayers, ...this._customLayers]) { if (layer.layer.options && values.includes(layer.layer.options.code)) { layer.enabled = true; } @@ -405,7 +452,7 @@ function enableConfig(control, {layers, customLayersOrder}) { customLayerExists: function(fieldValues, ignoreLayer) { const serialized = this.serializeCustomLayer(fieldValues); - for (let layer of this._customLayers()) { + for (let layer of this._customLayers) { if (layer !== ignoreLayer && layer.serialized === serialized) { return layer; } @@ -442,7 +489,6 @@ function enableConfig(control, {layers, customLayersOrder}) { const layer = this.createCustomLayer(fieldValues); layer.enabled = true; - layer.checked = ko.observable(true); this._customLayers.push(layer); this.hideCustomLayerForm(); this.updateLayers(); diff --git a/src/lib/leaflet.control.layers.configure/style.css b/src/lib/leaflet.control.layers.configure/style.css @@ -36,7 +36,7 @@ left: 50%; } -.leaflet-layers-select-window { +.leaflet-layers-config-window { position: relative; left: -50%; background-color: white; @@ -50,7 +50,7 @@ min-width: 290px; } -.leaflet-layers-select-window form { +.leaflet-layers-config-window form { overflow: auto; overflow: overlay; padding-right: 18px; @@ -59,18 +59,18 @@ /*width: auto ;*/ } -.leaflet-layers-select-window .buttons-row { +.leaflet-layers-config-window .buttons-row { position: absolute; bottom: 0; margin-bottom: 6px; white-space: nowrap; } -.leaflet-layers-select-window label { +.leaflet-layers-config-window label { display: block; } -.leaflet-layers-select-window .button, +.leaflet-layers-config-window .button, .custom-layers-window .button { display: inline-block; height: 26px; @@ -84,16 +84,16 @@ color: #333; } -.leaflet-layers-select-window .button:hover { +.leaflet-layers-config-window .button:hover { background-color: #f4f4f4; } -.leaflet-layers-select-window .section-header{ +.leaflet-layers-config-window .section-header{ font-size: 14px; font-weight: bold; } -.leaflet-layers-select-window .section-header a { +.leaflet-layers-config-window .section-header a { color: #666; }