chore: remove most of DomUtil/DomEvent from browser.js

This commit is contained in:
Yohan Boniface 2025-04-23 16:05:29 +02:00
parent bf2e9dc175
commit 60ac4b35f2
5 changed files with 128 additions and 97 deletions

View file

@ -5,6 +5,7 @@ import { translate } from './i18n.js'
import * as Icon from './rendering/icon.js' import * as Icon from './rendering/icon.js'
import ContextMenu from './ui/contextmenu.js' import ContextMenu from './ui/contextmenu.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
import { SCHEMA } from './schema.js'
export default class Browser { export default class Browser {
constructor(umap, leafletMap) { constructor(umap, leafletMap) {
@ -21,35 +22,24 @@ export default class Browser {
addFeature(feature, parent) { addFeature(feature, parent) {
if (feature.isFiltered()) return if (feature.isFiltered()) return
if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return if (this.options.inBbox && !feature.isOnScreen(this.bounds)) return
const row = DomUtil.create('li', `${feature.getClassName()} feature`) const template = `
const zoom_to = DomUtil.createButtonIcon( <li class="feature ${feature.getClassName()}">
row, <button class="icon icon-16 icon-zoom" title="${translate('Bring feature to center')}" data-ref=zoom></button>
'icon-zoom', <button class="icon icon-16 show-on-edit icon-edit" title="${translate('Edit this feature')}" data-ref=edit></button>
translate('Bring feature to center') <button class="icon icon-16 show-on-edit icon-delete" title="${translate('Delete this feature')}" data-ref=remove></button>
) <i class="icon icon-16 icon-${feature.getClassName()} feature-color" data-ref=colorBox></i>
const edit = DomUtil.createButtonIcon( <span class="feature-title" data-ref=label></span>
row, </li>
'show-on-edit icon-edit', `
translate('Edit this feature') const [row, { zoom, edit, remove, colorBox, label }] =
) Utils.loadTemplateWithRefs(template)
const del = DomUtil.createButtonIcon( label.textContent = label.title = feature.getDisplayName() || '—'
row,
'show-on-edit icon-delete',
translate('Delete this feature')
)
const colorBox = DomUtil.create(
'i',
`icon icon-16 icon-${feature.getClassName()} feature-color`,
row
)
const title = DomUtil.create('span', 'feature-title', row)
const symbol = feature._getIconUrl const symbol = feature._getIconUrl
? Icon.formatUrl(feature._getIconUrl(), feature) ? Icon.formatUrl(feature._getIconUrl(), feature)
: null : null
title.textContent = title.title = feature.getDisplayName() || '—'
const bgcolor = feature.getPreviewColor() const bgcolor = feature.getPreviewColor()
colorBox.style.backgroundColor = bgcolor colorBox.style.backgroundColor = bgcolor
if (symbol && symbol !== U.SCHEMA.iconUrl.default) { if (symbol && symbol !== SCHEMA.iconUrl.default) {
const icon = Icon.makeElement(symbol, colorBox) const icon = Icon.makeElement(symbol, colorBox)
Icon.setContrast(icon, colorBox, symbol, bgcolor) Icon.setContrast(icon, colorBox, symbol, bgcolor)
} else if (DomUtil.contrastedColor(colorBox, bgcolor)) { } else if (DomUtil.contrastedColor(colorBox, bgcolor)) {
@ -58,10 +48,10 @@ export default class Browser {
const viewFeature = (e) => { const viewFeature = (e) => {
feature.zoomTo({ ...e, callback: () => feature.view() }) feature.zoomTo({ ...e, callback: () => feature.view() })
} }
DomEvent.on(zoom_to, 'click', viewFeature) zoom.addEventListener('click', viewFeature)
DomEvent.on(title, 'click', viewFeature) label.addEventListener('click', viewFeature)
DomEvent.on(edit, 'click', feature.edit, feature) edit.addEventListener('click', () => feature.edit())
DomEvent.on(del, 'click', feature.del, feature) remove.addEventListener('click', () => feature.del())
// HOTFIX. Remove when this is released: // HOTFIX. Remove when this is released:
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
DomEvent.disableClickPropagation(row) DomEvent.disableClickPropagation(row)
@ -75,45 +65,51 @@ export default class Browser {
addDataLayer(datalayer, parent) { addDataLayer(datalayer, parent) {
let className = `datalayer ${datalayer.getHidableClass()}` let className = `datalayer ${datalayer.getHidableClass()}`
if (this.mode !== 'layers') className += ' show-list' if (this.mode !== 'layers') className += ' show-list'
const container = DomUtil.create('div', className, parent) const [container, { headline, toolbox, toggle, label }] =
const headline = DomUtil.create('h5', '', container) Utils.loadTemplateWithRefs(`
container.id = this.datalayerId(datalayer) <div class="${className}" id="${this.datalayerId(datalayer)}">
const ul = DomUtil.create('ul', '', container) <h5 data-ref=headline>
<i class="icon icon-16 datalayer-toggle-list" data-ref=toggle></i>
<span data-ref=toolbox></span>
<span class="datalayer-name" data-id="${datalayer.id}" data-ref=label></span>
<span class="datalayer-counter"></span>
</h5>
<ul></ul>
</div>
`)
datalayer.renderToolbox(toolbox)
parent.appendChild(container)
const toggleList = () => parent.classList.toggle('show-list')
toggle.addEventListener('click', toggleList)
label.addEventListener('click', toggleList)
this.updateDatalayer(datalayer) this.updateDatalayer(datalayer)
} }
updateDatalayer(datalayer) { updateDatalayer(datalayer) {
// Compute once, but use it for each feature later. // Compute once, but use it for each feature later.
this.bounds = this._leafletMap.getBounds() this.bounds = this._leafletMap.getBounds()
const parent = DomUtil.get(this.datalayerId(datalayer)) const id = this.datalayerId(datalayer)
const parent = document.getElementById(id)
// Panel is not open // Panel is not open
if (!parent) return if (!parent) return
parent.classList.toggle('off', !datalayer.isVisible()) parent.classList.toggle('off', !datalayer.isVisible())
const label = parent.querySelector('.datalayer-name')
const container = parent.querySelector('ul') const container = parent.querySelector('ul')
const headline = parent.querySelector('h5')
const toggleList = () => parent.classList.toggle('show-list')
headline.innerHTML = ''
const toggle = DomUtil.create('i', 'icon icon-16 datalayer-toggle-list', headline)
DomEvent.on(toggle, 'click', toggleList)
datalayer.renderToolbox(headline)
const name = DomUtil.create('span', 'datalayer-name', headline)
name.textContent = name.title = datalayer.options.name
DomEvent.on(name, 'click', toggleList)
container.innerHTML = '' container.innerHTML = ''
datalayer.eachFeature((feature) => this.addFeature(feature, container)) datalayer.eachFeature((feature) => this.addFeature(feature, container))
datalayer.propagate(['properties.name'])
const total = datalayer.count() const total = datalayer.count()
if (!total) return if (!total) return
const current = container.querySelectorAll('li').length const current = container.querySelectorAll('li').length
const count = total === current ? total : `${current}/${total}` const count = total === current ? total : `${current}/${total}`
const counter = DomUtil.create('span', 'datalayer-counter', headline) const counter = parent.querySelector('.datalayer-counter')
counter.textContent = `(${count})` counter.textContent = `(${count})`
counter.title = translate(`Features in this layer: ${count}`) counter.title = translate(`Features in this layer: ${count}`)
} }
toggleBadge() { toggleBadge() {
U.Utils.toggleBadge(this.filtersTitle, this.hasFilters()) Utils.toggleBadge(this.filtersTitle, this.hasFilters())
U.Utils.toggleBadge('.umap-control-browse', this.hasFilters()) Utils.toggleBadge('.umap-control-browse', this.hasFilters())
} }
onFormChange() { onFormChange() {
@ -157,21 +153,51 @@ export default class Browser {
open(mode) { open(mode) {
// Force only if mode is known, otherwise keep current mode. // Force only if mode is known, otherwise keep current mode.
if (mode) this.mode = mode if (mode) this.mode = mode
const container = DomUtil.create('div') const template = `
<div>
<h3><i class="icon icon-16 icon-layers"></i>${translate('Data browser')}</h3>
<details class="filters" data-ref="details">
<summary data-ref=filtersTitle><i class="icon icon-16 icon-filters"></i>${translate('Filters')}</summary>
<fieldset>
<div data-ref=formContainer>
</div>
<button class="flat" type="button" data-ref=reset><i class="icon icon-16 icon-restore" title=""></i>${translate('Reset all')}</button>
</fieldset>
</details>
<div class="main-toolbox">
<i class="icon icon-16 icon-eye" title="${translate('show/hide all layers')}" data-ref="toggle"></i>
<i class="icon icon-16 icon-zoom" title="${translate('zoom to data extent')}" data-ref="fitBounds"></i>
<i class="icon icon-16 icon-download" title="${translate('download visible data')}" data-ref="download"></i>
</div>
<div data-ref=dataContainer></div>
</div>
`
const [
container,
{
details,
filtersTitle,
toggle,
fitBounds,
download,
dataContainer,
formContainer,
reset,
},
] = Utils.loadTemplateWithRefs(template)
// HOTFIX. Remove when this is released: // HOTFIX. Remove when this is released:
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
DomEvent.disableClickPropagation(container) DomEvent.disableClickPropagation(container)
details.open = this.mode === 'filters'
toggle.addEventListener('click', () => this.toggleLayers())
fitBounds.addEventListener('click', () => this._umap.fitDataBounds())
download.addEventListener('click', () => this.downloadVisible(download))
download.hidden = this._umap.getProperty('embedControl') === false
DomUtil.createTitle(container, translate('Data browser'), 'icon-layers') this.filtersTitle = filtersTitle
this.formContainer = DomUtil.createFieldset(container, L._('Filters'), { this.dataContainer = dataContainer
on: this.mode === 'filters', this.formContainer = formContainer
className: 'filters',
icon: 'icon-filters',
})
this.filtersTitle = container.querySelector('summary')
this.toggleBadge() this.toggleBadge()
this.addMainToolbox(container)
this.dataContainer = DomUtil.create('div', '', container)
let fields = [ let fields = [
[ [
@ -184,27 +210,19 @@ export default class Browser {
builder.on('set', () => this.onFormChange()) builder.on('set', () => this.onFormChange())
let filtersBuilder let filtersBuilder
this.formContainer.appendChild(builder.build()) this.formContainer.appendChild(builder.build())
DomEvent.on(builder.form, 'reset', () => { builder.form.addEventListener('reset', () => {
window.setTimeout(builder.syncAll.bind(builder)) window.setTimeout(builder.syncAll.bind(builder))
}) })
if (this._umap.properties.facetKey) { if (this._umap.properties.facetKey) {
fields = this._umap.facets.build() fields = this._umap.facets.build()
filtersBuilder = new Form(this._umap.facets, fields) filtersBuilder = new Form(this._umap.facets, fields)
filtersBuilder.on('set', () => this.onFormChange()) filtersBuilder.on('set', () => this.onFormChange())
DomEvent.on(filtersBuilder.form, 'reset', () => { filtersBuilder.form.addEventListener('reset', () => {
window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder)) window.setTimeout(filtersBuilder.syncAll.bind(filtersBuilder))
}) })
this.formContainer.appendChild(filtersBuilder.build()) this.formContainer.appendChild(filtersBuilder.build())
} }
const reset = DomUtil.createButton('flat', this.formContainer, '', () => reset.addEventListener('click', () => this.resetFilters())
this.resetFilters()
)
DomUtil.createIcon(reset, 'icon-restore')
DomUtil.element({
tagName: 'span',
parent: reset,
textContent: translate('Reset all'),
})
this._umap.panel.open({ this._umap.panel.open({
content: container, content: container,
@ -220,21 +238,6 @@ export default class Browser {
} }
} }
addMainToolbox(container) {
const [toolbox, { toggle, fitBounds, download }] = Utils.loadTemplateWithRefs(`
<div class="main-toolbox">
<i class="icon icon-16 icon-eye" title="${translate('show/hide all layers')}" data-ref="toggle"></i>
<i class="icon icon-16 icon-zoom" title="${translate('zoom to data extent')}" data-ref="fitBounds"></i>
<i class="icon icon-16 icon-download" title="${translate('download visible data')}" data-ref="download"></i>
</div>
`)
container.appendChild(toolbox)
toggle.addEventListener('click', () => this.toggleLayers())
fitBounds.addEventListener('click', () => this._umap.fitDataBounds())
download.addEventListener('click', () => this.downloadVisible(download))
download.hidden = this._umap.getProperty('embedControl') === false
}
downloadVisible(element) { downloadVisible(element) {
const menu = new ContextMenu({ fixed: true }) const menu = new ContextMenu({ fixed: true })
const items = [] const items = []
@ -265,15 +268,13 @@ export default class Browser {
} }
static backButton(umap) { static backButton(umap) {
const button = DomUtil.createButtonIcon( const button = Utils.loadTemplate(
DomUtil.create('li', '', undefined), `<button class="icon icon-16 icon-back" title="${translate('Back to browser')}"></button>`
'icon-back',
translate('Back to browser')
) )
// Fixme: remove me when this is merged and released // Fixme: remove me when this is merged and released
// https://github.com/Leaflet/Leaflet/pull/9052 // https://github.com/Leaflet/Leaflet/pull/9052
DomEvent.disableClickPropagation(button) DomEvent.disableClickPropagation(button)
DomEvent.on(button, 'click', () => umap.openBrowser()) button.addEventListener('click', () => umap.openBrowser())
return button return button
} }
} }

View file

@ -130,6 +130,10 @@ export class DataLayer {
} }
render(fields, builder) { render(fields, builder) {
// Propagate will remove the fields it has already
// processed
fields = this.propagate(fields)
const impacts = Utils.getImpactsFromSchema(fields) const impacts = Utils.getImpactsFromSchema(fields)
for (const impact of impacts) { for (const impact of impacts) {
@ -153,6 +157,29 @@ export class DataLayer {
} }
} }
// This method does a targeted update of the UI,
// it whould be merged with `render`` method and the
// SCHEMA at some point
propagate(fields = []) {
const impacts = {
'properties.name': () => {
Utils.eachElement('.datalayer-name', (el) => {
if (el.dataset.id === this.id) {
el.textContent = this.getName()
el.title = this.getName()
}
})
},
}
for (const [field, impact] of Object.entries(impacts)) {
if (!fields.length || fields.includes(field)) {
impact()
fields = fields.filter((item) => item !== field)
}
}
return fields
}
showAtLoad() { showAtLoad() {
return this.autoLoaded() && this.showAtZoom() return this.autoLoaded() && this.showAtZoom()
} }

View file

@ -99,7 +99,10 @@ const BaseButton = Control.extend({
</div> </div>
` `
const [container, { button }] = Utils.loadTemplateWithRefs(template) const [container, { button }] = Utils.loadTemplateWithRefs(template)
button.addEventListener('click', () => this.onClick()) button.addEventListener('click', (event) => {
event.stopPropagation()
this.onClick()
})
button.addEventListener('dblclick', (event) => { button.addEventListener('dblclick', (event) => {
event.stopPropagation() event.stopPropagation()
}) })

View file

@ -465,19 +465,19 @@ def test_main_toolbox_toggle_all_layers(live_server, map, page):
expect(page.locator(".datalayer.off")).to_have_count(1) expect(page.locator(".datalayer.off")).to_have_count(1)
# Click on button # Click on button
page.locator(".umap-browser [data-ref=toggle]").click() page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
# Should have hidden the two other layers # Should have hidden the two other layers
expect(page.locator(".datalayer.off")).to_have_count(3) expect(page.locator(".datalayer.off")).to_have_count(3)
expect(markers).to_have_count(0) expect(markers).to_have_count(0)
# Click again # Click again
page.locator(".umap-browser [data-ref=toggle]").click() page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
# Should shown all layers # Should shown all layers
expect(page.locator(".datalayer.off")).to_have_count(0) expect(page.locator(".datalayer.off")).to_have_count(0)
expect(markers).to_have_count(3) expect(markers).to_have_count(3)
# Click again # Click again
page.locator(".umap-browser [data-ref=toggle]").click() page.locator(".umap-browser").get_by_title("Show/hide all layers").click()
# Should hidden again all layers # Should hidden again all layers
expect(page.locator(".datalayer.off")).to_have_count(3) expect(page.locator(".datalayer.off")).to_have_count(3)
expect(markers).to_have_count(0) expect(markers).to_have_count(0)

View file

@ -180,9 +180,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page):
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0) first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1) second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2) third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
assert "X Third" == first_listed_feature.text_content() assert "X Third" == first_listed_feature.text_content().strip()
assert "Y Second" == second_listed_feature.text_content() assert "Y Second" == second_listed_feature.text_content().strip()
assert "Z First" == third_listed_feature.text_content() assert "Z First" == third_listed_feature.text_content().strip()
# Change the default sortkey to be "key" # Change the default sortkey to be "key"
page.get_by_role("button", name="Edit").click() page.get_by_role("button", name="Edit").click()
@ -201,9 +201,9 @@ def test_sortkey_impacts_datalayerindex(map, live_server, page):
first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0) first_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(0)
second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1) second_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(1)
third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2) third_listed_feature = page.locator(".umap-browser .datalayer ul > li").nth(2)
assert "Z First" == first_listed_feature.text_content() assert "Z First" == first_listed_feature.text_content().strip()
assert "Y Second" == second_listed_feature.text_content() assert "Y Second" == second_listed_feature.text_content().strip()
assert "X Third" == third_listed_feature.text_content() assert "X Third" == third_listed_feature.text_content().strip()
def test_hover_tooltip_setting_should_be_persistent(live_server, map, page): def test_hover_tooltip_setting_should_be_persistent(live_server, map, page):