From 53f93ee97e8bb7a6988a52cf58996c479c944afd Mon Sep 17 00:00:00 2001 From: Yohan Boniface Date: Wed, 17 Apr 2024 18:17:44 +0200 Subject: [PATCH] chore: move facets to a dedicated module --- umap/static/umap/js/modules/facets.js | 126 ++++++++++++++++++++++++++ umap/static/umap/js/modules/global.js | 2 + umap/static/umap/js/umap.controls.js | 112 +---------------------- umap/static/umap/js/umap.core.js | 2 +- umap/static/umap/js/umap.features.js | 5 +- umap/static/umap/js/umap.js | 16 +--- 6 files changed, 133 insertions(+), 130 deletions(-) create mode 100644 umap/static/umap/js/modules/facets.js diff --git a/umap/static/umap/js/modules/facets.js b/umap/static/umap/js/modules/facets.js new file mode 100644 index 00000000..d141e1de --- /dev/null +++ b/umap/static/umap/js/modules/facets.js @@ -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._('') + 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 + }, {}) + } +} diff --git a/umap/static/umap/js/modules/global.js b/umap/static/umap/js/modules/global.js index 7d07ba2f..16742508 100644 --- a/umap/static/umap/js/modules/global.js +++ b/umap/static/umap/js/modules/global.js @@ -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, diff --git a/umap/static/umap/js/umap.controls.js b/umap/static/umap/js/umap.controls.js index 884633dc..384e7e31 100644 --- a/umap/static/umap/js/umap.controls.js +++ b/umap/static/umap/js/umap.controls.js @@ -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 () { diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 06313859..aae48995 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -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 diff --git a/umap/static/umap/js/umap.features.js b/umap/static/umap/js/umap.features.js index b6fce1b7..25f2ea39 100644 --- a/umap/static/umap/js/umap.features.js +++ b/umap/static/umap/js/umap.features.js @@ -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._("") if (choices?.length && (!value || !choices.includes(value))) return false } } diff --git a/umap/static/umap/js/umap.js b/umap/static/umap/js/umap.js index 7f4bdc90..e61741ff 100644 --- a/umap/static/umap/js/umap.js +++ b/umap/static/umap/js/umap.js @@ -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) => {