From 9abbfbc01ecd0f9a37132ec2fbdcaf0071cafe6b Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 2 Aug 2023 07:59:37 +0200 Subject: [PATCH] Use Leaflet.FormBuilder to build advanced filters form --- umap/static/umap/js/umap.controls.js | 113 ++++++++------------------- umap/static/umap/js/umap.features.js | 7 +- umap/static/umap/js/umap.forms.js | 30 ++++++- 3 files changed, 69 insertions(+), 81 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 15d357bb..8550c098 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -789,113 +789,68 @@ L.U.Map.include({ L.DomEvent.on(filter, 'input', appendAll, this) L.DomEvent.on(filter, 'input', resetLayers, this) - this.ui.openPanel({ data: { html: browserContainer }, actions: [this._aboutLink()] }) + this.ui.openPanel({ + data: { html: browserContainer }, + actions: [this._aboutLink()], + }) }, _openFilter: function () { - const filterContainer = L.DomUtil.create('div', 'umap-filter-data'), - title = L.DomUtil.add( - 'h3', - 'umap-filter-title', - filterContainer, - this.options.name - ), + const container = L.DomUtil.create('div', 'umap-filter-data'), + title = L.DomUtil.add('h3', 'umap-filter-title', container, this.options.name), propertiesContainer = L.DomUtil.create( 'div', 'umap-filter-properties', - filterContainer + container ), - advancedFilterKeys = this.getAdvancedFilterKeys() + keys = this.getAdvancedFilterKeys() - const advancedFiltersFull = {} - let filtersAlreadyLoaded = true - if (!this.options.advancedFilters) { - this.options.advancedFilters = {} - filtersAlreadyLoaded = false - } - advancedFilterKeys.forEach((property) => { - advancedFiltersFull[property] = [] - if (!filtersAlreadyLoaded || !this.options.advancedFilters[property]) { - this.options.advancedFilters[property] = [] - } + const knownValues = {} + if (!this.options.advancedFilters) this.options.advancedFilters = {} + + keys.forEach((key) => { + knownValues[key] = [] + if (!this.options.advancedFilters[key]) + this.options.advancedFilters[key] = [] }) + this.eachBrowsableDataLayer((datalayer) => { datalayer.eachFeature((feature) => { - advancedFilterKeys.forEach((property) => { - if (feature.properties[property]) { - if (!advancedFiltersFull[property].includes(feature.properties[property])) { - advancedFiltersFull[property].push(feature.properties[property]) - } + keys.forEach((key) => { + let value = feature.properties[key] + if (typeof value !== 'undefined' && !knownValues[key].includes(value)) { + knownValues[key].push(value) } }) }) }) - const addPropertyValue = function (property, value) { - const property_li = L.DomUtil.create('li', ''), - filter_check = L.DomUtil.create('input', '', property_li), - filter_label = L.DomUtil.create('label', '', property_li) - filter_check.type = 'checkbox' - filter_check.id = `checkbox_${property}_${value}` - filter_check.checked = - this.options.advancedFilters[property] && - this.options.advancedFilters[property].includes(value) - filter_check.dataset.property = property - filter_check.dataset.value = value - filter_label.htmlFor = `checkbox_${property}_${value}` - filter_label.innerHTML = value - L.DomEvent.on( - filter_check, - 'change', - function (e) { - const property = e.srcElement.dataset.property - const value = e.srcElement.dataset.value - if (e.srcElement.checked) { - this.options.advancedFilters[property].push(value) - } else { - this.options.advancedFilters[property].splice( - this.options.advancedFilters[property].indexOf(value), - 1 - ) - } - L.bind(filterFeatures, this)() - }, - this - ) - return property_li - } - - const addProperty = function (property) { - const container = L.DomUtil.create( - 'div', - 'property-container', - propertiesContainer - ), - headline = L.DomUtil.add('h5', '', container, property) - const ul = L.DomUtil.create('ul', '', container) - const orderedValues = advancedFiltersFull[property] - orderedValues.sort() - orderedValues.forEach((value) => { - ul.appendChild(L.bind(addPropertyValue, this)(property, value)) - }) - } - const filterFeatures = function () { let found = false this.eachBrowsableDataLayer((datalayer) => { datalayer.resetLayer(true) if (datalayer.hasDataVisible()) found = true }) + // TODO: display a results counter in the panel instead. if (!found) this.ui.alert({ content: L._('No results for these filters'), level: 'info' }) } - propertiesContainer.innerHTML = '' - advancedFilterKeys.forEach((property) => { - L.bind(addProperty, this)(property) + const fields = keys.map((current) => [ + `options.advancedFilters.${current}`, + { + handler: 'AdvancedFilter', + choices: knownValues[current], + }, + ]) + const builder = new L.U.FormBuilder(this, fields, { + makeDirty: false, + callback: filterFeatures, + callbackContext: this, }) + container.appendChild(builder.build()) - this.ui.openPanel({ data: { html: filterContainer }, actions: [this._aboutLink()] }) + this.ui.openPanel({ data: { html: container }, actions: [this._aboutLink()] }) }, _aboutLink: function () { diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index cf5aa938..2eddf185 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -87,7 +87,12 @@ L.U.FeatureMixin = { edit: function (e) { if (!this.map.editEnabled || this.isReadOnly()) return const container = L.DomUtil.create('div', 'umap-feature-container') - L.DomUtil.add('h3', `umap-feature-properties ${this.getClassName()}`, container, L._('Feature properties')) + L.DomUtil.add( + 'h3', + `umap-feature-properties ${this.getClassName()}`, + container, + L._('Feature properties') + ) let builder = new L.U.FormBuilder(this, ['datalayer'], { callback: function () { diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index bb8e73a4..5bf8eb32 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -684,6 +684,34 @@ L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({ }, }) +L.FormBuilder.AdvancedFilter = L.FormBuilder.Element.extend({ + build: function () { + this.container = L.DomUtil.create('div', 'property-container', this.parentNode) + this.headline = L.DomUtil.add('h5', '', this.container, this.name) + this.ul = L.DomUtil.create('ul', '', this.container) + const choices = this.options.choices + choices.sort() + choices.forEach((value) => this.buildLi(value)) + }, + + buildLi: function (value) { + const property_li = L.DomUtil.create('li', '', this.ul), + input = L.DomUtil.create('input', '', property_li), + label = L.DomUtil.create('label', '', property_li) + input.type = 'checkbox' + input.id = `checkbox_${this.name}_${value}` + input.checked = this.get().includes(value) + input.dataset.value = value + label.htmlFor = `checkbox_${this.name}_${value}` + label.innerHTML = value + L.DomEvent.on(input, 'change', (e) => this.sync()) + }, + + toJS: function () { + return [...this.ul.querySelectorAll('input:checked')].map(i => i.dataset.value) + }, +}) + L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({ default: 'null', className: 'umap-multiplechoice', @@ -1125,7 +1153,7 @@ L.U.FormBuilder = L.FormBuilder.extend({ setter: function (field, value) { L.FormBuilder.prototype.setter.call(this, field, value) - this.obj.isDirty = true + if (this.options.makeDirty !== false) this.obj.isDirty = true }, finish: function () {