chore: move facets to a dedicated module

This commit is contained in:
Yohan Boniface 2024-04-17 18:17:44 +02:00
parent 47c6473285
commit 53f93ee97e
6 changed files with 133 additions and 130 deletions

View file

@ -0,0 +1,126 @@
import { DomUtil, DomEvent, stamp } from '../../vendors/leaflet/leaflet-src.esm.js'
import { translate } from './i18n.js'
export default class Facets {
constructor(map) {
this.map = map
this.selected = {}
}
compute(names, defined) {
const properties = {}
names.forEach((name) => {
const type = defined[name]['type']
properties[name] = { type: type }
this.selected[name] = { type: type }
if (!['date', 'datetime', 'number'].includes(type)) {
properties[name].choices = []
this.selected[name].choices = []
}
})
this.map.eachBrowsableDataLayer((datalayer) => {
datalayer.eachFeature((feature) => {
names.forEach((name) => {
let value = feature.properties[name]
const type = defined[name]['type']
switch (type) {
case 'date':
case 'datetime':
case 'number':
value = type === 'number' ? parseFloat(value) : new Date(value)
if (!isNaN(value)) {
if (isNaN(properties[name].min) || properties[name].min > value) {
properties[name].min = value
}
if (isNaN(properties[name].max) || properties[name].max < value) {
properties[name].max = value
}
}
break
default:
value = String(value || '') || L._('<empty value>')
if (!properties[name].choices.includes(value)) {
properties[name].choices.push(value)
}
}
})
})
})
return properties
}
open() {
const container = L.DomUtil.create('div', 'umap-facet-search')
const title = L.DomUtil.add(
'h3',
'umap-filter-title',
container,
L._('Facet search')
)
const defined = this.getDefined()
const names = Object.keys(defined)
const facetProperties = this.compute(names, defined)
const filterFeatures = function () {
let found = false
this.map.eachBrowsableDataLayer((datalayer) => {
datalayer.resetLayer(true)
if (datalayer.hasDataVisible()) found = true
})
// TODO: display a results counter in the panel instead.
if (!found) {
this.map.ui.alert({
content: L._('No results for these facets'),
level: 'info',
})
}
}
const fields = names.map((name) => {
let criteria = facetProperties[name]
let handler = 'FacetSearchChoices'
switch (criteria['type']) {
case 'number':
handler = 'FacetSearchNumber'
break
case 'date':
handler = 'FacetSearchDate'
break
case 'datetime':
handler = 'FacetSearchDateTime'
break
}
let label = defined[name]['label']
return [
`selected.${name}`,
{
criteria: criteria,
handler: handler,
label: label,
},
]
})
const builder = new U.FormBuilder(this, fields, {
makeDirty: false,
callback: filterFeatures,
callbackContext: this,
})
container.appendChild(builder.build())
this.map.panel.open({ content: container })
}
getDefined() {
const defaultType = 'checkbox'
const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime']
return (this.map.options.facetKey || '').split(',').reduce((acc, curr) => {
let [name, label, type] = curr.split('|')
type = allowedTypes.includes(type) ? type : defaultType
acc[name] = { label: label || name, type: type }
return acc
}, {})
}
}

View file

@ -1,5 +1,6 @@
import URLs from './urls.js'
import Browser from './browser.js'
import Facets from './facets.js'
import { Panel, EditPanel, FullPanel } from './panel.js'
import * as Utils from './utils.js'
import { SCHEMA } from './schema.js'
@ -17,6 +18,7 @@ window.U = {
HTTPError,
NOKError,
Browser,
Facets,
Panel,
EditPanel,
FullPanel,

View file

@ -661,117 +661,7 @@ const ControlsMixin = {
'tilelayers',
],
_openFacet: function () {
const container = L.DomUtil.create('div', 'umap-facet-search'),
title = L.DomUtil.add('h3', 'umap-filter-title', container, L._('Facet search')),
facetKeys = this.getFacetKeys(),
keys = Object.keys(facetKeys)
const facetCriteria = {}
keys.forEach((key) => {
const type = facetKeys[key]['type']
if (['date', 'datetime', 'number'].includes(type)) {
if (!facetCriteria[key])
facetCriteria[key] = {
type: facetKeys[key]['type'],
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] = {
type: facetKeys[key]['type'],
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]
const type = facetKeys[key]['type']
if (['date', 'datetime', 'number'].includes(type)) {
value = value != null ? value : undefined
if (['date', 'datetime'].includes(type)) value = new Date(value)
if (['number'].includes(type)) value = parseFloat(value)
if (
!isNaN(value) &&
(isNaN(facetCriteria[key]['min']) || facetCriteria[key]['min'] > value)
) {
facetCriteria[key]['min'] = value
}
if (
!isNaN(value) &&
(isNaN(facetCriteria[key]['max']) || facetCriteria[key]['max'] < value)
) {
facetCriteria[key]['max'] = value
}
} else {
value = String(value)
value = value.length ? value : L._('empty string')
if (!!value && !facetCriteria[key]['choices'].includes(value)) {
facetCriteria[key]['choices'].push(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 facets'), level: 'info' })
}
}
const fields = keys.map((key) => {
let criteria = facetCriteria[key]
let handler = 'FacetSearchChoices'
switch (criteria['type']) {
case 'number':
handler = 'FacetSearchNumber'
break
case 'date':
handler = 'FacetSearchDate'
break
case 'datetime':
handler = 'FacetSearchDateTime'
break
}
let label = facetKeys[key]['label']
return [
`facets.${key}`,
{
criteria: criteria,
handler: handler,
label: label,
},
]
})
const builder = new U.FormBuilder(this, fields, {
makeDirty: false,
callback: filterFeatures,
callbackContext: this,
})
container.appendChild(builder.build())
this.panel.open({ content: container })
this.facets.open()
},
displayCaption: function () {

View file

@ -73,7 +73,7 @@ L.DomUtil.add = (tagName, className, container, content) => {
if (content.nodeType && content.nodeType === 1) {
el.appendChild(content)
} else {
el.innerHTML = content
el.textContent = content
}
}
return el

View file

@ -493,7 +493,7 @@ U.FeatureMixin = {
},
matchFacets: function () {
const facets = this.map.facets
const facets = this.map.facets.selected
for (const [property, criteria] of Object.entries(facets)) {
let value = this.properties[property]
const type = criteria["type"]
@ -515,8 +515,7 @@ U.FeatureMixin = {
if (!isNaN(max) && !isNaN(value) && max < value) return false
} else {
const choices = criteria["choices"]
value = String(value)
value = (value.length ? value : L._("empty string"))
value = String(value || '') || L._("<empty value>")
if (choices?.length && (!value || !choices.includes(value))) return false
}
}

View file

@ -116,7 +116,6 @@ U.Map = L.Map.extend({
this.datalayers_index = []
this.dirty_datalayers = []
this.features_index = {}
this.facets = {}
// Needed for actions labels
this.help = new U.Help(this)
@ -377,6 +376,7 @@ U.Map = L.Map.extend({
if (this.options.scrollWheelZoom) this.scrollWheelZoom.enable()
else this.scrollWheelZoom.disable()
this.browser = new U.Browser(this)
this.facets = new U.Facets(this)
this.importer = new U.Importer(this)
this.drop = new U.DropControl(this)
this.share = new U.Share(this)
@ -1846,20 +1846,6 @@ U.Map = L.Map.extend({
return (this.options.filterKey || this.options.sortKey || 'name').split(',')
},
getFacetKeys: function () {
const defaultType = 'checkbox'
const allowedTypes = [defaultType, 'radio', 'number', 'date', 'datetime']
return (this.options.facetKey || '').split(',').reduce((acc, curr) => {
let [key, label, type] = curr.split('|')
type = allowedTypes.includes(type) ? type : defaultType
acc[key] = {
label: label || key,
type: type,
}
return acc
}, {})
},
getLayersBounds: function () {
const bounds = new L.latLngBounds()
this.eachBrowsableDataLayer((d) => {