chore: move facets to a dedicated module
This commit is contained in:
parent
47c6473285
commit
53f93ee97e
6 changed files with 133 additions and 130 deletions
126
umap/static/umap/js/modules/facets.js
Normal file
126
umap/static/umap/js/modules/facets.js
Normal 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
|
||||
}, {})
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Loading…
Reference in a new issue