Support date and number properties in facet search - refactoring
This commit is contained in:
parent
839bb4c5d8
commit
b0c9edba7f
5 changed files with 112 additions and 80 deletions
|
@ -669,26 +669,25 @@ const ControlsMixin = {
|
||||||
const facetCriteria = {}
|
const facetCriteria = {}
|
||||||
|
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
if (facetKeys[key]["dataType"] === "date") {
|
const inputType = facetKeys[key]["inputType"]
|
||||||
|
if (["date", "datetime-local", "number"].includes(inputType)) {
|
||||||
if (!facetCriteria[key]) facetCriteria[key] = {
|
if (!facetCriteria[key]) facetCriteria[key] = {
|
||||||
"dataType": facetKeys[key]["dataType"],
|
|
||||||
"inputType": facetKeys[key]["inputType"],
|
"inputType": facetKeys[key]["inputType"],
|
||||||
"min": undefined,
|
"min": undefined,
|
||||||
"max": undefined
|
"max": undefined
|
||||||
}
|
}
|
||||||
if (!this.facets[key]) this.facets[key] = {
|
if (!this.facets[key]) this.facets[key] = {
|
||||||
"dataType": facetKeys[key]["dataType"],
|
"inputType": facetKeys[key]["inputType"],
|
||||||
"min": undefined,
|
"min": undefined,
|
||||||
"max": undefined
|
"max": undefined
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!facetCriteria[key]) facetCriteria[key] = {
|
if (!facetCriteria[key]) facetCriteria[key] = {
|
||||||
"dataType": facetKeys[key]["dataType"],
|
|
||||||
"inputType": facetKeys[key]["inputType"],
|
"inputType": facetKeys[key]["inputType"],
|
||||||
"choices": []
|
"choices": []
|
||||||
}
|
}
|
||||||
if (!this.facets[key]) this.facets[key] = {
|
if (!this.facets[key]) this.facets[key] = {
|
||||||
"dataType": facetKeys[key]["dataType"],
|
"inputType": facetKeys[key]["inputType"],
|
||||||
"choices": []
|
"choices": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -698,15 +697,20 @@ const ControlsMixin = {
|
||||||
datalayer.eachFeature((feature) => {
|
datalayer.eachFeature((feature) => {
|
||||||
keys.forEach((key) => {
|
keys.forEach((key) => {
|
||||||
let value = feature.properties[key]
|
let value = feature.properties[key]
|
||||||
if (facetKeys[key]["dataType"] === "date") {
|
const inputType = facetKeys[key]["inputType"]
|
||||||
value = L.Util.parseDateField(value)
|
if (["date", "datetime-local", "number"].includes(inputType)) {
|
||||||
if (!!value && (!facetCriteria[key]["min"] || facetCriteria[key]["min"] > value)) {
|
value = (value != null ? value : undefined)
|
||||||
|
if (["date", "datetime-local"].includes(inputType)) value = new Date(value);
|
||||||
|
if (["number"].includes(inputType)) value = parseFloat(value);
|
||||||
|
if (!isNaN(value) && (isNaN(facetCriteria[key]["min"]) || facetCriteria[key]["min"] > value)) {
|
||||||
facetCriteria[key]["min"] = value
|
facetCriteria[key]["min"] = value
|
||||||
}
|
}
|
||||||
if (!!value && (!facetCriteria[key]["max"] || facetCriteria[key]["max"] < value)) {
|
if (!isNaN(value) && (isNaN(facetCriteria[key]["max"]) || facetCriteria[key]["max"] < value)) {
|
||||||
facetCriteria[key]["max"] = value
|
facetCriteria[key]["max"] = value
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
value = String(value)
|
||||||
|
value = (value.length ? value : "empty string")
|
||||||
if (!!value && !facetCriteria[key]["choices"].includes(value)) {
|
if (!!value && !facetCriteria[key]["choices"].includes(value)) {
|
||||||
facetCriteria[key]["choices"].push(value)
|
facetCriteria[key]["choices"].push(value)
|
||||||
}
|
}
|
||||||
|
@ -725,11 +729,10 @@ const ControlsMixin = {
|
||||||
if (!found)
|
if (!found)
|
||||||
this.ui.alert({ content: L._('No results for these facets'), level: 'info' })
|
this.ui.alert({ content: L._('No results for these facets'), level: 'info' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = keys.map((key) => [
|
const fields = keys.map((key) => [
|
||||||
`facets.${key}`,
|
`facets.${key}`,
|
||||||
{
|
{
|
||||||
handler: facetCriteria[key]["inputType"] === "datetime-local" ? 'FacetSearchDate' : 'FacetSearchCheckbox',
|
handler: ["date", "datetime-local", "number"].includes(facetCriteria[key]["inputType"]) ? 'FacetSearchMinMax' : 'FacetSearchChoices',
|
||||||
criteria: facetCriteria[key],
|
criteria: facetCriteria[key],
|
||||||
label: facetKeys[key]["label"]
|
label: facetKeys[key]["label"]
|
||||||
},
|
},
|
||||||
|
|
|
@ -67,18 +67,6 @@ L.Util.setNullableBooleanFromQueryString = function (options, name) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
L.Util.parseDateField = function (value) {
|
|
||||||
if (value != null && parseFloat(value).toString() === value.toString()) {
|
|
||||||
value = parseFloat(value);
|
|
||||||
if (Math.abs(value) < 10000000000) {
|
|
||||||
value = value * 1000;
|
|
||||||
} else if (Math.abs(value) > 10000000000000) {
|
|
||||||
value = value / 1000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Date(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
L.DomUtil.add = (tagName, className, container, content) => {
|
L.DomUtil.add = (tagName, className, container, content) => {
|
||||||
const el = L.DomUtil.create(tagName, className, container)
|
const el = L.DomUtil.create(tagName, className, container)
|
||||||
if (content) {
|
if (content) {
|
||||||
|
@ -553,7 +541,7 @@ U.Help = L.Class.extend({
|
||||||
slugKey: L._('The name of the property to use as feature unique identifier.'),
|
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'),
|
filterKey: L._('Comma separated list of properties to use when filtering features'),
|
||||||
facetKey: L._(
|
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). To control data type, add it after another | (eg.: mykey|My Key|enum,otherkey|Other Key|date). Allowed values for the data type are date and enum (default). To control input field type, add it after another | (eg.: mykey|My Key|enum|checkbox,otherkey|Other Key|date|datetime-local). Allowed values for the input field type are checkbox (default) or radio for data type enum and datetime-local (default) for data type date.'
|
'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 input field type, add it after another | (eg.: mykey|My Key|checkbox,otherkey|Other Key|datetime). Allowed values for the input field type are checkbox (default), radio, number, date and datetime.'
|
||||||
),
|
),
|
||||||
interactive: L._(
|
interactive: L._(
|
||||||
'If false, the polygon or line will act as a part of the underlying map.'
|
'If false, the polygon or line will act as a part of the underlying map.'
|
||||||
|
|
|
@ -496,15 +496,27 @@ U.FeatureMixin = {
|
||||||
const facets = this.map.facets
|
const facets = this.map.facets
|
||||||
for (const [property, criteria] of Object.entries(facets)) {
|
for (const [property, criteria] of Object.entries(facets)) {
|
||||||
let value = this.properties[property]
|
let value = this.properties[property]
|
||||||
const dataType = criteria["dataType"]
|
const inputType = criteria["inputType"]
|
||||||
if (dataType === "date") {
|
if (["date", "datetime-local", "number"].includes(inputType)) {
|
||||||
const min = new Date(criteria["min"])
|
let min = criteria["min"]
|
||||||
const max = new Date(criteria["max"])
|
let max = criteria["max"]
|
||||||
value = L.Util.parseDateField(value)
|
value = (value != null ? value : undefined)
|
||||||
if (!!min && (!value || min > value)) return false
|
if (["date", "datetime-local"].includes(inputType)) {
|
||||||
if (!!max && (!value || max < value)) return false
|
min = new Date(min)
|
||||||
|
max = new Date(max)
|
||||||
|
value = new Date(value)
|
||||||
|
}
|
||||||
|
if (["number"].includes(inputType)) {
|
||||||
|
min = parseFloat(min)
|
||||||
|
max = parseFloat(max)
|
||||||
|
value = parseFloat(value)
|
||||||
|
}
|
||||||
|
if (!isNaN(min) && !isNaN(value) && min > value) return false
|
||||||
|
if (!isNaN(max) && !isNaN(value) && max < value) return false
|
||||||
} else {
|
} else {
|
||||||
const choices = criteria["choices"]
|
const choices = criteria["choices"]
|
||||||
|
value = String(value)
|
||||||
|
value = (value.length ? value : "empty string")
|
||||||
if (choices.length && (!value || !choices.includes(value))) return false
|
if (choices.length && (!value || !choices.includes(value))) return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -744,11 +744,10 @@ L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
L.FormBuilder.FacetSearchCheckbox = L.FormBuilder.Element.extend({
|
L.FormBuilder.FacetSearchChoices = L.FormBuilder.Element.extend({
|
||||||
build: function () {
|
build: function () {
|
||||||
this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode)
|
this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode)
|
||||||
this.ul = L.DomUtil.create('ul', '', this.container)
|
this.ul = L.DomUtil.create('ul', '', this.container)
|
||||||
this.dataType = this.options.criteria["dataType"]
|
|
||||||
this.inputType = this.options.criteria["inputType"]
|
this.inputType = this.options.criteria["inputType"]
|
||||||
|
|
||||||
const choices = this.options.criteria["choices"]
|
const choices = this.options.criteria["choices"]
|
||||||
|
@ -778,54 +777,87 @@ L.FormBuilder.FacetSearchCheckbox = L.FormBuilder.Element.extend({
|
||||||
|
|
||||||
toJS: function () {
|
toJS: function () {
|
||||||
return {
|
return {
|
||||||
'dataType': this.dataType,
|
'inputType': this.inputType,
|
||||||
'choices': [...this.ul.querySelectorAll('input:checked')].map((i) => i.dataset.value)
|
'choices': [...this.ul.querySelectorAll('input:checked')].map((i) => i.dataset.value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
L.FormBuilder.FacetSearchDate = L.FormBuilder.Element.extend({
|
L.FormBuilder.FacetSearchMinMax = L.FormBuilder.Element.extend({
|
||||||
build: function () {
|
build: function () {
|
||||||
this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode);
|
this.container = L.DomUtil.create('div', 'umap-facet', this.parentNode)
|
||||||
this.table = L.DomUtil.create('table', '', this.container);
|
this.table = L.DomUtil.create('table', '', this.container)
|
||||||
this.dataType = this.options.criteria["dataType"];
|
this.inputType = this.options.criteria["inputType"]
|
||||||
this.inputType = this.options.criteria["inputType"];
|
|
||||||
|
|
||||||
const min = this.options.criteria['min'];
|
const min = this.options.criteria['min']
|
||||||
const max = this.options.criteria['max'];
|
const max = this.options.criteria['max']
|
||||||
|
|
||||||
this.minTr = L.DomUtil.create('tr', '', this.table);
|
this.minTr = L.DomUtil.create('tr', '', this.table)
|
||||||
|
|
||||||
this.minTdLabel = L.DomUtil.create('td', '', this.minTr);
|
this.minTdLabel = L.DomUtil.create('td', '', this.minTr)
|
||||||
this.minLabel = L.DomUtil.create('label', '', this.minTdLabel);
|
this.minLabel = L.DomUtil.create('label', '', this.minTdLabel)
|
||||||
this.minLabel.innerHTML = 'From';
|
this.minLabel.innerHTML = 'Min'
|
||||||
this.minLabel.htmlFor = `${this.inputType}_${this.name}_min`;
|
this.minLabel.htmlFor = `${this.inputType}_${this.name}_min`
|
||||||
|
|
||||||
this.minTdInput = L.DomUtil.create('td', '', this.minTr);
|
this.minTdInput = L.DomUtil.create('td', '', this.minTr)
|
||||||
this.minInput = L.DomUtil.create('input', '', this.minTdInput);
|
this.minInput = L.DomUtil.create('input', '', this.minTdInput)
|
||||||
this.minInput.type = this.inputType;
|
this.minInput.type = this.inputType
|
||||||
this.minInput.step = '0.001';
|
this.minInput.id = `${this.inputType}_${this.name}_min`
|
||||||
this.minInput.id = `${this.inputType}_${this.name}_min`;
|
this.minInput.step = '1'
|
||||||
this.minInput.valueAsNumber = (min.valueOf() - min.getTimezoneOffset() * 60000);;
|
if (min != null) {
|
||||||
this.minInput.dataset.value = min;
|
this.minInput.valueAsNumber = min.valueOf()
|
||||||
|
this.minInput.dataset.value = min
|
||||||
|
}
|
||||||
|
|
||||||
this.maxTr = L.DomUtil.create('tr', '', this.table);
|
this.maxTr = L.DomUtil.create('tr', '', this.table)
|
||||||
|
|
||||||
this.maxTdLabel = L.DomUtil.create('td', '', this.maxTr);
|
this.maxTdLabel = L.DomUtil.create('td', '', this.maxTr)
|
||||||
this.maxLabel = L.DomUtil.create('label', '', this.maxTdLabel);
|
this.maxLabel = L.DomUtil.create('label', '', this.maxTdLabel)
|
||||||
this.maxLabel.innerHTML = 'Until';
|
this.maxLabel.innerHTML = 'Max'
|
||||||
this.maxLabel.htmlFor = `${this.inputType}_${this.name}_max`;
|
this.maxLabel.htmlFor = `${this.inputType}_${this.name}_max`
|
||||||
|
|
||||||
this.maxTdInput = L.DomUtil.create('td', '', this.maxTr);
|
this.maxTdInput = L.DomUtil.create('td', '', this.maxTr)
|
||||||
this.maxInput = L.DomUtil.create('input', '', this.maxTdInput);
|
this.maxInput = L.DomUtil.create('input', '', this.maxTdInput)
|
||||||
this.maxInput.type = this.inputType;
|
this.maxInput.type = this.inputType
|
||||||
this.maxInput.step = '0.001';
|
this.maxInput.id = `${this.inputType}_${this.name}_max`
|
||||||
this.maxInput.id = `${this.inputType}_${this.name}_max`;
|
this.maxInput.step = '1'
|
||||||
this.maxInput.valueAsNumber = (max.valueOf() - max.getTimezoneOffset() * 60000);;
|
if (max != null) {
|
||||||
this.maxInput.dataset.value = max;
|
this.maxInput.valueAsNumber = max.valueOf()
|
||||||
|
this.maxInput.dataset.value = max
|
||||||
|
}
|
||||||
|
|
||||||
L.DomEvent.on(this.minInput, 'change', (e) => this.sync());
|
if (["date", "datetime-local"].includes(this.inputType)) {
|
||||||
L.DomEvent.on(this.maxInput, 'change', (e) => this.sync());
|
this.minLabel.innerHTML = 'From'
|
||||||
|
this.maxLabel.innerHTML = 'Until'
|
||||||
|
if (min != null) {
|
||||||
|
this.minInput.valueAsNumber = (min.valueOf() - min.getTimezoneOffset() * 60000)
|
||||||
|
}
|
||||||
|
if (max != null) {
|
||||||
|
this.maxInput.valueAsNumber = (max.valueOf() - max.getTimezoneOffset() * 60000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["datetime-local"].includes(this.inputType)) {
|
||||||
|
this.minInput.step = '0.001'
|
||||||
|
this.maxInput.step = '0.001'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (["number"].includes(this.inputType)) {
|
||||||
|
if (min != null && max != null) {
|
||||||
|
// calculate step from significant digits of min and max values
|
||||||
|
let minStep = String(min).replace(/^\d+?(0*)((\.)(\d*?)0*|)$/, "1$1$3$4").split('.')
|
||||||
|
let maxStep = String(max).replace(/^\d+?(0*)((\.)(\d*?)0*|)$/, "1$1$3$4").split('.')
|
||||||
|
minStep = parseFloat((minStep[1] || "").replace(/\d/g, "0").replace(/^0/, "0.0").replace(/0$/, "1") || (minStep[0] || "").replace(/0$/, "") || "1")
|
||||||
|
maxStep = parseFloat((maxStep[1] || "").replace(/\d/g, "0").replace(/^0/, "0.0").replace(/0$/, "1") || (maxStep[0] || "").replace(/0$/, "") || "1")
|
||||||
|
|
||||||
|
const step = Math.min(minStep, maxStep)
|
||||||
|
this.minInput.step = String(step)
|
||||||
|
this.maxInput.step = String(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
L.DomEvent.on(this.minInput, 'change', (e) => this.sync())
|
||||||
|
L.DomEvent.on(this.maxInput, 'change', (e) => this.sync())
|
||||||
},
|
},
|
||||||
|
|
||||||
buildLabel: function () {
|
buildLabel: function () {
|
||||||
|
@ -834,7 +866,7 @@ L.FormBuilder.FacetSearchDate = L.FormBuilder.Element.extend({
|
||||||
|
|
||||||
toJS: function () {
|
toJS: function () {
|
||||||
return {
|
return {
|
||||||
'dataType': this.dataType,
|
'inputType': this.inputType,
|
||||||
'min': this.minInput.value,
|
'min': this.minInput.value,
|
||||||
'max': this.maxInput.value,
|
'max': this.maxInput.value,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1846,25 +1846,22 @@ U.Map = L.Map.extend({
|
||||||
},
|
},
|
||||||
|
|
||||||
getFacetKeys: function () {
|
getFacetKeys: function () {
|
||||||
const allowedTypes = {
|
const allowedInputTypes = {
|
||||||
"enum": ["checkbox", "radio"],
|
"checkbox": "checkbox",
|
||||||
"date": ["datetime-local"],
|
"radio": "radio",
|
||||||
|
"number": "number",
|
||||||
|
"date": "date",
|
||||||
|
"datetime": "datetime-local",
|
||||||
}
|
}
|
||||||
console.log(this.options.facetKey)
|
|
||||||
return (this.options.facetKey || '').split(',').reduce((acc, curr) => {
|
return (this.options.facetKey || '').split(',').reduce((acc, curr) => {
|
||||||
const els = curr.split('|')
|
const els = curr.split('|')
|
||||||
acc[els[0]] = {
|
acc[els[0]] = {
|
||||||
"label": els[1] || els[0],
|
"label": els[1] || els[0],
|
||||||
"dataType": (
|
"inputType": (
|
||||||
(els[2] in allowedTypes) ? els[2] :
|
(els[2] in allowedInputTypes) ? allowedInputTypes[els[2]] :
|
||||||
Object.keys(allowedTypes)[0]
|
Object.values(allowedInputTypes)[0]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
acc[els[0]]["inputType"] = (
|
|
||||||
allowedTypes[acc[els[0]]["dataType"]].includes(els[3]) ? els[3] :
|
|
||||||
allowedTypes[acc[els[0]]["dataType"]][0]
|
|
||||||
)
|
|
||||||
console.log(acc)
|
|
||||||
return acc
|
return acc
|
||||||
}, {})
|
}, {})
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in a new issue