From 6cfb65baf0819231e28020bea48f17e7ad08938f Mon Sep 17 00:00:00 2001 From: flammermann Date: Wed, 27 Dec 2023 23:29:26 +0000 Subject: [PATCH] Support date properties in facet search --- umap/static/umap/js/umap.controls.js | 52 +++++++++++++++++++------ umap/static/umap/js/umap.core.js | 2 +- umap/static/umap/js/umap.features.js | 26 +++++++++++-- umap/static/umap/js/umap.forms.js | 57 ++++++++++++++++++++++++++-- umap/static/umap/js/umap.js | 7 +++- 5 files changed, 121 insertions(+), 23 deletions(-) diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index ab25569b..a8302425 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -741,22 +741,50 @@ L.U.Map.include({ _openFacet: function () { const container = L.DomUtil.create('div', 'umap-facet-search'), title = L.DomUtil.add('h3', 'umap-filter-title', container, L._('Facet search')), - keys = Object.keys(this.getFacetKeys()) + facetKeys = this.getFacetKeys(), + keys = Object.keys(facetKeys) - const knownValues = {} + const facetCriteria = {} keys.forEach((key) => { - knownValues[key] = [] - if (!this.facets[key]) this.facets[key] = [] + if (facetKeys[key]["type"] === "date") { + if (!facetCriteria[key]) facetCriteria[key] = { + "min": undefined, + "max": undefined + } + if (!this.facets[key]) this.facets[key] = { + "type": facetKeys[key]["type"], + "min": undefined, + "max": undefined + } + } else { + if (!facetCriteria[key]) facetCriteria[key] = { + "choices": [] + } + if (!this.facets[key]) this.facets[key] = { + "type": facetKeys[key]["type"], + "choices": [] + } + } }) this.eachBrowsableDataLayer((datalayer) => { datalayer.eachFeature((feature) => { keys.forEach((key) => { let value = feature.properties[key] - if (typeof value !== 'undefined' && !knownValues[key].includes(value)) { - knownValues[key].push(value) - } + if (facetKeys[key]["type"] === "date") { + value = feature.parseDateField(value) + if (!!value && (!facetCriteria[key]["min"] || facetCriteria[key]["min"] > value)) { + facetCriteria[key]["min"] = value + } + if (!!value && (!facetCriteria[key]["max"] || facetCriteria[key]["max"] < value)) { + facetCriteria[key]["max"] = value + } + } else { + if (!!value && !facetCriteria[key]["choices"].includes(value)) { + facetCriteria[key]["choices"].push(value) + } + } }) }) }) @@ -772,12 +800,12 @@ L.U.Map.include({ this.ui.alert({ content: L._('No results for these facets'), level: 'info' }) } - const fields = keys.map((current) => [ - `facets.${current}`, + const fields = keys.map((key) => [ + `facets.${key}`, { - handler: 'FacetSearch', - choices: knownValues[current], - label: this.getFacetKeys()[current], + handler: facetKeys[key]["type"] === "date" ? 'FacetSearchDate' : 'FacetSearchCheckbox', + criteria: facetCriteria[key], + label: facetKeys[key]["label"] }, ]) const builder = new L.U.FormBuilder(this, fields, { diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 9dd50fa9..636f17af 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -706,7 +706,7 @@ L.U.Help = L.Class.extend({ slugKey: L._('The name of the property to use as feature unique identifier.'), filterKey: L._('Comma separated list of properties to use when filtering features'), facetKey: L._( - 'Comma separated list of properties to use for facet search (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key)' + 'Comma separated list of properties to use for facet search (eg.: mykey,otherkey). To control label, add it after a | (eg.: mykey|My Key,otherkey|Other Key). To control type, add it after another | (eg.: mykey|My Key|checkbox,otherkey|Other Key|date)' ), interactive: L._('If false, the polygon or line will act as a part of the underlying map.'), outlink: L._('Define link to open in a new window on polygon click.'), diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index 8e826131..5fe24b47 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -483,12 +483,30 @@ L.U.FeatureMixin = { return false }, + parseDateField: function (value) { + if (parseFloat(value).toString() === value.toString()) { + value = parseFloat(value); + if (value < 10000000000) { + value = value * 1000; + } + } + return new Date(value); + }, + matchFacets: function () { const facets = this.map.facets - for (const [property, expected] of Object.entries(facets)) { - if (expected.length) { - let value = this.properties[property] - if (!value || !expected.includes(value)) return false + for (const [property, criteria] of Object.entries(facets)) { + let value = this.properties[property] + const type = criteria["type"] + if (type === "date") { + const min = new Date(criteria["min"]) + const max = new Date(criteria["max"]) + value = this.parseDateField(value) + if (!!min && (!value || min > value)) return false + if (!!max && (!value || max < value)) return false + } else { + const choices = criteria["choices"] + if (choices.length && (!value || !choices.includes(value))) return false } } return true diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js index fb7a41c1..fca96210 100644 --- a/umap/static/umap/js/umap.forms.js +++ b/umap/static/umap/js/umap.forms.js @@ -785,11 +785,11 @@ L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({ }, }) -L.FormBuilder.FacetSearch = L.FormBuilder.Element.extend({ +L.FormBuilder.FacetSearchCheckbox = L.FormBuilder.Element.extend({ build: function () { this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode) this.ul = L.DomUtil.create('ul', '', this.container) - const choices = this.options.choices + const choices = this.options.criteria["choices"] choices.sort() choices.forEach((value) => this.buildLi(value)) }, @@ -804,7 +804,7 @@ L.FormBuilder.FacetSearch = L.FormBuilder.Element.extend({ label = L.DomUtil.create('label', '', property_li) input.type = 'checkbox' input.id = `checkbox_${this.name}_${value}` - input.checked = this.get().includes(value) + input.checked = this.get()['choices'].includes(value) input.dataset.value = value label.htmlFor = `checkbox_${this.name}_${value}` label.innerHTML = value @@ -812,10 +812,59 @@ L.FormBuilder.FacetSearch = L.FormBuilder.Element.extend({ }, toJS: function () { - return [...this.ul.querySelectorAll('input:checked')].map((i) => i.dataset.value) + return { + 'type': 'checkbox', + 'choices': [...this.ul.querySelectorAll('input:checked')].map((i) => i.dataset.value) + } }, }) +L.FormBuilder.FacetSearchDate = L.FormBuilder.Element.extend({ + build: function () { + this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode); + const min = this.options.criteria['min']; + const max = this.options.criteria['max']; + + // Create labels for min and max inputs + this.minLabel = L.DomUtil.create('label', '', this.container); + this.minLabel.innerHTML = 'Start'; + this.minLabel.htmlFor = `date_${this.name}_min`; + + this.minInput = L.DomUtil.create('input', '', this.container); + this.minInput.type = 'datetime-local'; + this.minInput.step = '0.001'; + this.minInput.id = `date_${this.name}_min`; + this.minInput.valueAsNumber = (min.valueOf() - min.getTimezoneOffset() * 60000);; + this.minInput.dataset.value = min; + + this.maxLabel = L.DomUtil.create('label', '', this.container); + this.maxLabel.innerHTML = 'End'; + this.maxLabel.htmlFor = `date_${this.name}_max`; + + this.maxInput = L.DomUtil.create('input', '', this.container); + this.maxInput.type = 'datetime-local'; + this.maxInput.step = '0.001'; + this.maxInput.id = `date_${this.name}_max`; + this.maxInput.valueAsNumber = (max.valueOf() - max.getTimezoneOffset() * 60000);; + this.maxInput.dataset.value = max; + + L.DomEvent.on(this.minInput, 'change', (e) => this.sync()); + L.DomEvent.on(this.maxInput, 'change', (e) => this.sync()); + }, + + buildLabel: function () { + this.label = L.DomUtil.add('h5', '', this.parentNode, this.options.label) + }, + + toJS: function () { + return { + 'type': 'date', + 'min': this.minInput.value, + 'max': this.maxInput.value, + }; + }, +}); + L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({ default: 'null', className: 'umap-multiplechoice', diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index add161cf..2e17d805 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -1364,7 +1364,7 @@ L.U.Map.include({ { handler: 'Input', helpEntries: 'facetKey', - placeholder: L._('Example: key1,key2,key3'), + placeholder: L._('Example: key1,key2|Label 2,key3|Label 3|checkbox'), label: L._('Facet keys'), }, ], @@ -2033,7 +2033,10 @@ L.U.Map.include({ getFacetKeys: function () { return (this.options.facetKey || '').split(',').reduce((acc, curr) => { const els = curr.split('|') - acc[els[0]] = els[1] || els[0] + acc[els[0]] = { + "label": els[1] || els[0], + "type": els[2] || "checkbox" + } return acc }, {}) },