wip(forms): refactor forms templating

This commit is contained in:
Yohan Boniface 2025-01-07 16:58:44 +01:00
parent e0fadea749
commit 176b8bdbcc
8 changed files with 242 additions and 201 deletions

View file

@ -1,3 +1,4 @@
.umap-form-inline .formbox,
.umap-form-inline { .umap-form-inline {
display: inline; display: inline;
} }
@ -559,7 +560,6 @@ i.info {
clear: both; clear: both;
margin-bottom: 20px; margin-bottom: 20px;
overflow: hidden; overflow: hidden;
display: none;
} }
.umap-color-picker span { .umap-color-picker span {
width: 20px; width: 20px;

View file

@ -655,7 +655,7 @@ export class DataLayer extends ServerStored {
{ {
label: translate('Data is browsable'), label: translate('Data is browsable'),
handler: 'Switch', handler: 'Switch',
helpEntries: 'browsable', helpEntries: ['browsable'],
}, },
], ],
[ [

View file

@ -1,6 +1,7 @@
import getClass from './fields.js' import getClass from './fields.js'
import * as Utils from '../utils.js' import * as Utils from '../utils.js'
import { SCHEMA } from '../schema.js' import { SCHEMA } from '../schema.js'
import { translate } from '../i18n.js'
export class Form { export class Form {
constructor(obj, fields, properties) { constructor(obj, fields, properties) {
@ -35,9 +36,8 @@ export class Form {
} }
buildField(field) { buildField(field) {
field.buildLabel() field.buildTemplate()
field.build() field.build()
field.buildHelpText()
} }
makeField(field) { makeField(field) {
@ -115,6 +115,14 @@ export class Form {
} }
finish() {} finish() {}
getTemplate(helper) {
return `
<div class="formbox" data-ref=container>
${helper.getTemplate()}
<small class="help-text" data-ref=helpText></small>
</div>`
}
} }
export class MutatingForm extends Form { export class MutatingForm extends Form {
@ -190,6 +198,40 @@ export class MutatingForm extends Form {
} }
} }
getTemplate(helper) {
let template
if (helper.properties.inheritable) {
const extraClassName = helper.get(true) === undefined ? ' undefined' : ''
template = `
<div class="umap-field-${helper.name} formbox inheritable${extraClassName}">
<div class="header" data-ref=header>
<a href="#" class="button undefine" data-ref=undefine>${translate('clear')}</a>
<a href="#" class="button define" data-ref=define>${translate('define')}</a>
<span class="quick-actions show-on-defined" data-ref=actions></span>
${helper.getLabelTemplate()}
</div>
<div class="show-on-defined" data-ref=container>
${helper.getTemplate()}
<small class="help-text" data-ref=helpText></small>
</div>
</div>`
} else {
template = `
<div class="formbox umap-field-${helper.name}" data-ref=container>
${helper.getLabelTemplate()}
${helper.getTemplate()}
<small class="help-text" data-ref=helpText></small>
</div>`
}
return template
}
build() {
super.build()
this._umap.help.parse(this.form)
return this.form
}
finish(helper) { finish(helper) {
helper.input?.blur() helper.input?.blur()
} }

View file

@ -25,53 +25,55 @@ class BaseElement {
this.setProperties(properties) this.setProperties(properties)
this.fieldEls = this.field.split('.') this.fieldEls = this.field.split('.')
this.name = this.builder.getName(field) this.name = this.builder.getName(field)
this.parentNode = this.getParentNode() this.id = `${this.builder.properties.id || Date.now()}.${this.name}`
}
getDefaultProperties() {
return {}
} }
setProperties(properties) { setProperties(properties) {
this.properties = Object.assign({}, this.properties, properties) this.properties = Object.assign(
this.getDefaultProperties(),
this.properties,
properties
)
} }
onDefine() {} onDefine() {}
getParentNode() { buildTemplate() {
const classNames = ['formbox'] const template = this.builder.getTemplate(this)
if (this.properties.inheritable) { const [root, elements] = Utils.loadTemplateWithRefs(template)
classNames.push('inheritable') this.root = root
if (this.get(true) === undefined) classNames.push('undefined') this.elements = elements
this.container = elements.container
this.form.appendChild(this.root)
}
getTemplate() {
return ''
}
build() {
if (this.properties.helpText) {
this.elements.helpText.textContent = this.properties.helpText
} else {
this.elements.helpText.hidden = true
} }
classNames.push(`umap-field-${this.name}`)
const [wrapper, { header, define, undefine, quickContainer, container }] = if (this.elements.define) {
Utils.loadTemplateWithRefs(` this.elements.define.addEventListener('click', (event) => {
<div>
<div class="header" data-ref=header>
<a href="#" class="button undefine" data-ref=undefine>${translate('clear')}</a>
<a href="#" class="button define" data-ref=define>${translate('define')}</a>
<span class="quick-actions show-on-defined" data-ref=quickContainer></span>
</div>
<div class="show-on-defined" data-ref=container></div>
</div>`)
this.wrapper = wrapper
this.wrapper.classList.add(...classNames)
this.header = header
this.form.appendChild(this.wrapper)
if (this.properties.inheritable) {
define.addEventListener('click', (event) => {
event.preventDefault() event.preventDefault()
event.stopPropagation() event.stopPropagation()
this.fetch() this.fetch()
this.onDefine() this.onDefine()
this.wrapper.classList.remove('undefined') this.root.classList.remove('undefined')
}) })
undefine.addEventListener('click', () => this.undefine())
} else {
define.hidden = true
undefine.hidden = true
} }
if (this.elements.undefine) {
this.quickContainer = quickContainer this.elements.undefine.addEventListener('click', () => this.undefine())
this.extendedContainer = container }
return this.extendedContainer
} }
clear() { clear() {
@ -102,44 +104,12 @@ class BaseElement {
this.builder.setter(this.field, this.toJS()) this.builder.setter(this.field, this.toJS())
} }
getLabelParent() { getLabelTemplate() {
return this.header const label = this.properties.label
} const help = this.properties.helpEntries?.join() || ''
return label
getHelpTextParent() { ? `<label title="${label}" data-ref=label data-help="${help}">${label}</label>`
return this.parentNode : ''
}
buildLabel() {
if (this.properties.label) {
const label = this.properties.label
this.label = Utils.loadTemplate(`<label title="${label}">${label}</label>`)
const parent = this.getLabelParent()
parent.appendChild(this.label)
if (this.properties.helpEntries) {
this.builder._umap.help.button(this.label, this.properties.helpEntries)
} else if (this.properties.helpTooltip) {
const info = Utils.loadTemplate('<i class="info"></i>')
this.label.appendChild(info)
info.addEventListener('mouseover', () => {
this.builder._umap.tooltip.open({
anchor: info,
content: this.properties.helpTooltip,
position: 'top',
})
})
}
}
}
buildHelpText() {
if (this.properties.helpText) {
const container = Utils.loadTemplate(
`<small class="help-text">${Utils.escapeHTML(this.properties.helpText)}</small>`
)
const parent = this.getHelpTextParent()
parent.appendChild(container)
}
} }
fetch() {} fetch() {}
@ -154,35 +124,35 @@ class BaseElement {
} }
undefine() { undefine() {
this.wrapper.classList.add('undefined') this.root.classList.add('undefined')
this.clear() this.clear()
this.sync() this.sync()
} }
} }
Fields.Textarea = class extends BaseElement { Fields.Textarea = class extends BaseElement {
getTemplate() {
return `<textarea placeholder="${this.properties.placeholder || ''}" data-ref=textarea></textarea>`
}
build() { build() {
this.input = Utils.loadTemplate('<textarea></textarea>') super.build()
if (this.properties.className) this.input.classList.add(this.properties.className) this.textarea = this.elements.textarea
if (this.properties.placeholder) {
this.input.placeholder = this.properties.placeholder
}
this.parentNode.appendChild(this.input)
this.fetch() this.fetch()
this.input.addEventListener('input', () => this.sync()) this.textarea.addEventListener('input', () => this.sync())
this.input.addEventListener('keypress', (event) => this.onKeyPress(event)) this.textarea.addEventListener('keypress', (event) => this.onKeyPress(event))
} }
fetch() { fetch() {
const value = this.toHTML() const value = this.toHTML()
this.initial = value this.initial = value
if (value) { if (value) {
this.input.value = value this.textarea.value = value
} }
} }
value() { value() {
return this.input.value return this.textarea.value
} }
onKeyPress(event) { onKeyPress(event) {
@ -195,18 +165,17 @@ Fields.Textarea = class extends BaseElement {
} }
Fields.Input = class extends BaseElement { Fields.Input = class extends BaseElement {
getTemplate() {
return `<input type="${this.type()}" name="${this.name}" placeholder="${this.properties.placeholder || ''}" data-ref=input />`
}
build() { build() {
this.input = Utils.loadTemplate('<input />') super.build()
this.parentNode.appendChild(this.input) this.input = this.elements.input
this.input.type = this.type()
this.input.name = this.name
this.input._helper = this this.input._helper = this
if (this.properties.className) { if (this.properties.className) {
this.input.classList.add(this.properties.className) this.input.classList.add(this.properties.className)
} }
if (this.properties.placeholder) {
this.input.placeholder = this.properties.placeholder
}
if (this.properties.min !== undefined) { if (this.properties.min !== undefined) {
this.input.min = this.properties.min this.input.min = this.properties.min
} }
@ -254,11 +223,13 @@ Fields.BlurInput = class extends Fields.Input {
return 'blur' return 'blur'
} }
getTemplate() {
return `${super.getTemplate()}<span class="button blur-button"></span>`
}
build() { build() {
this.properties.className = 'blur' this.properties.className = 'blur'
super.build() super.build()
const button = Utils.loadTemplate('<span class="button blur-button"></span>')
this.input.parentNode.insertBefore(button, this.input.nextSibling)
this.input.addEventListener('focus', () => this.fetch()) this.input.addEventListener('focus', () => this.fetch())
} }
@ -305,31 +276,29 @@ const FloatMixin = (Base) =>
} }
Fields.FloatInput = class extends FloatMixin(Fields.Input) { Fields.FloatInput = class extends FloatMixin(Fields.Input) {
// options: { // TODO use public class properties when in baseline
// step: 'any', getDefaultProperties() {
// } return { step: 'any' }
}
} }
Fields.BlurFloatInput = class extends FloatMixin(Fields.BlurInput) { Fields.BlurFloatInput = class extends FloatMixin(Fields.BlurInput) {
// options: { getDefaultProperties() {
// step: 'any', return { step: 'any' }
// }, }
} }
Fields.CheckBox = class extends BaseElement { Fields.CheckBox = class extends BaseElement {
getTemplate() {
return `<input type=checkbox name="${this.name}" data-ref=input />`
}
build() { build() {
const container = Utils.loadTemplate('<div class="checkbox-wrapper"></div>') this.input = this.elements.input
this.parentNode.appendChild(container)
this.input = Utils.loadTemplate('<input />')
container.appendChild(this.input)
if (this.properties.className) {
this.input.classList.add(this.properties.className)
}
this.input.type = 'checkbox'
this.input.name = this.name
this.input._helper = this this.input._helper = this
this.fetch() this.fetch()
this.input.addEventListener('change', () => this.sync()) this.input.addEventListener('change', () => this.sync())
super.build()
} }
fetch() { fetch() {
@ -338,7 +307,7 @@ Fields.CheckBox = class extends BaseElement {
} }
value() { value() {
return this.wrapper.classList.contains('undefined') ? undefined : this.input.checked return this.root.classList.contains('undefined') ? undefined : this.input.checked
} }
toHTML() { toHTML() {
@ -351,12 +320,16 @@ Fields.CheckBox = class extends BaseElement {
} }
Fields.Select = class extends BaseElement { Fields.Select = class extends BaseElement {
getTemplate() {
return `<select name="${this.name}" data-ref=select></select>`
}
build() { build() {
this.select = Utils.loadTemplate(`<select name="${this.name}"></select>`) this.select = this.elements.select
this.parentNode.appendChild(this.select)
this.validValues = [] this.validValues = []
this.buildOptions() this.buildOptions()
this.select.addEventListener('change', () => this.sync()) this.select.addEventListener('change', () => this.sync())
super.build()
} }
getOptions() { getOptions() {
@ -380,7 +353,7 @@ Fields.Select = class extends BaseElement {
const option = Utils.loadTemplate('<option></option>') const option = Utils.loadTemplate('<option></option>')
this.select.appendChild(option) this.select.appendChild(option)
option.value = value option.value = value
option.innerHTML = label option.textContent = label
if (this.toHTML() === value) { if (this.toHTML() === value) {
option.selected = 'selected' option.selected = 'selected'
} }
@ -444,21 +417,23 @@ Fields.NullableBoolean = class extends Fields.Select {
} }
Fields.EditableText = class extends BaseElement { Fields.EditableText = class extends BaseElement {
getTemplate() {
return `<span contentEditable class="${this.properties.className || ''}" data-ref=input></span>`
}
buildTemplate() {
// No wrapper at all
const template = this.getTemplate()
this.input = Utils.loadTemplate(template)
this.form.appendChild(this.input)
}
build() { build() {
this.input = Utils.loadTemplate(
`<span class="${this.properties.className || ''}"></span>`
)
this.parentNode.appendChild(this.input)
this.input.contentEditable = true
this.fetch() this.fetch()
this.input.addEventListener('input', () => this.sync()) this.input.addEventListener('input', () => this.sync())
this.input.addEventListener('keypress', (event) => this.onKeyPress(event)) this.input.addEventListener('keypress', (event) => this.onKeyPress(event))
} }
getParentNode() {
return this.form
}
value() { value() {
return this.input.textContent return this.input.textContent
} }
@ -480,17 +455,18 @@ Fields.ColorPicker = class extends Fields.Input {
return Utils.COLORS return Utils.COLORS
} }
getParentNode() { getDefaultProperties() {
super.getParentNode() return {
return this.quickContainer placeholder: translate('Inherit'),
}
}
getTemplate() {
return `${super.getTemplate()}<div class="umap-color-picker" hidden data-ref=colors></div>`
} }
build() { build() {
super.build() super.build()
this.input.placeholder = this.properties.placeholder || translate('Inherit')
this.container = Utils.loadTemplate('<div class="umap-color-picker"></div>')
this.extendedContainer.appendChild(this.container)
this.container.style.display = 'none'
for (const color of this.getColors()) { for (const color of this.getColors()) {
this.addColor(color) this.addColor(color)
} }
@ -506,16 +482,21 @@ Fields.ColorPicker = class extends Fields.Input {
} }
onFocus() { onFocus() {
this.container.style.display = 'block' this.showPicker()
this.spreadColor() this.spreadColor()
} }
showPicker() {
this.elements.colors.hidden = false
}
closePicker() {
this.elements.colors.hidden = true
}
onBlur() { onBlur() {
const closePicker = () => {
this.container.style.display = 'none'
}
// We must leave time for the click to be listened. // We must leave time for the click to be listened.
window.setTimeout(closePicker, 100) window.setTimeout(() => this.closePicker(), 100)
} }
sync() { sync() {
@ -530,12 +511,12 @@ Fields.ColorPicker = class extends Fields.Input {
addColor(colorName) { addColor(colorName) {
const span = Utils.loadTemplate('<span></span>') const span = Utils.loadTemplate('<span></span>')
this.container.appendChild(span) this.elements.colors.appendChild(span)
span.style.backgroundColor = span.title = colorName span.style.backgroundColor = span.title = colorName
const updateColorInput = () => { const updateColorInput = () => {
this.input.value = colorName this.input.value = colorName
this.sync() this.sync()
this.container.style.display = 'none' this.closePicker()
} }
span.addEventListener('mousedown', updateColorInput) span.addEventListener('mousedown', updateColorInput)
} }
@ -680,21 +661,25 @@ Fields.IconUrl = class extends Fields.BlurInput {
return 'hidden' return 'hidden'
} }
build() { getTemplate() {
super.build() return `
const [container, { buttons, tabs, body, footer }] = Utils.loadTemplateWithRefs(`
<div> <div>
<div data-ref=buttons></div> <div data-ref=buttons></div>
<div class="flat-tabs" data-ref=tabs></div> <div class="flat-tabs" data-ref=tabs></div>
<div class="umap-pictogram-body" data-ref=body></div> <div class="umap-pictogram-body" data-ref=body>
${super.getTemplate()}
</div>
<div data-ref=footer></div> <div data-ref=footer></div>
</div> </div>
`) `
this.parentNode.appendChild(container) }
this.buttons = buttons
this.tabs = tabs build() {
this.body = body super.build()
this.footer = footer this.buttons = this.elements.buttons
this.tabs = this.elements.tabs
this.body = this.elements.body
this.footer = this.elements.footer
this.updatePreview() this.updatePreview()
} }
@ -936,23 +921,27 @@ Fields.Url = class extends Fields.Input {
} }
Fields.Switch = class extends Fields.CheckBox { Fields.Switch = class extends Fields.CheckBox {
getParentNode() { getTemplate() {
super.getParentNode() const label = this.properties.label
if (this.properties.inheritable) return this.quickContainer return `${super.getTemplate()}<label title="${label}" for="${this.id}" data-ref=customLabel>${label}</label>`
return this.extendedContainer
} }
build() { build() {
super.build() super.build()
if (this.properties.inheritable) { // We have it in our template
this.label = Utils.loadTemplate('<label></label>') if (!this.properties.inheritable) {
// We already have the label near the switch,
// only show the default label in inheritable mode
// as the switch itself may be hidden (until "defined")
if (this.elements.label) {
this.elements.label.hidden = true
this.elements.label.innerHTML = ''
this.elements.label.title = ''
}
} }
this.input.parentNode.appendChild(this.label) this.container.classList.add('with-switch')
this.input.parentNode.classList.add('with-switch')
const id = `${this.builder.properties.id || Date.now()}.${this.name}`
this.label.setAttribute('for', id)
this.input.classList.add('switch') this.input.classList.add('switch')
this.input.id = id this.input.id = this.id
} }
} }
@ -961,22 +950,22 @@ Fields.FacetSearchBase = class extends BaseElement {
} }
Fields.FacetSearchChoices = class extends Fields.FacetSearchBase { Fields.FacetSearchChoices = class extends Fields.FacetSearchBase {
build() { getTemplate() {
const [container, { ul, label }] = Utils.loadTemplateWithRefs(` return `
<fieldset class="umap-facet"> <fieldset class="umap-facet">
<legend data-ref=label>${Utils.escapeHTML(this.properties.label)}</legend> <legend data-ref=label>${Utils.escapeHTML(this.properties.label)}</legend>
<ul data-ref=ul></ul> <ul data-ref=ul></ul>
</fieldset> </fieldset>
`) `
this.container = container }
this.ul = ul
this.label = label build() {
this.parentNode.appendChild(this.container)
this.type = this.properties.criteria.type this.type = this.properties.criteria.type
const choices = this.properties.criteria.choices const choices = this.properties.criteria.choices
choices.sort() choices.sort()
choices.forEach((value) => this.buildLi(value)) choices.forEach((value) => this.buildLi(value))
super.build()
} }
buildLi(value) { buildLi(value) {
@ -993,13 +982,13 @@ Fields.FacetSearchChoices = class extends Fields.FacetSearchBase {
input.checked = this.get().choices.includes(value) input.checked = this.get().choices.includes(value)
input.dataset.value = value input.dataset.value = value
input.addEventListener('change', () => this.sync()) input.addEventListener('change', () => this.sync())
this.ul.appendChild(li) this.elements.ul.appendChild(li)
} }
toJS() { toJS() {
return { return {
type: this.type, type: this.type,
choices: [...this.ul.querySelectorAll('input:checked')].map( choices: [...this.elements.ul.querySelectorAll('input:checked')].map(
(i) => i.dataset.value (i) => i.dataset.value
), ),
} }
@ -1019,28 +1008,30 @@ Fields.MinMaxBase = class extends Fields.FacetSearchBase {
return value.valueOf() return value.valueOf()
} }
build() { getTemplate() {
const [minLabel, maxLabel] = this.getLabels() const [minLabel, maxLabel] = this.getLabels()
const { min, max, type } = this.properties.criteria const { min, max, type } = this.properties.criteria
const { min: modifiedMin, max: modifiedMax } = this.get()
const currentMin = modifiedMin !== undefined ? modifiedMin : min
const currentMax = modifiedMax !== undefined ? modifiedMax : max
this.type = type this.type = type
const inputType = this.getInputType(this.type) const inputType = this.getInputType(this.type)
const minHTML = this.prepareForHTML(min) const minHTML = this.prepareForHTML(min)
const maxHTML = this.prepareForHTML(max) const maxHTML = this.prepareForHTML(max)
const [container, { minInput, maxInput }] = Utils.loadTemplateWithRefs(` return `
<fieldset class="umap-facet"> <fieldset class="umap-facet">
<legend>${Utils.escapeHTML(this.properties.label)}</legend> <legend>${Utils.escapeHTML(this.properties.label)}</legend>
<label>${minLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=minInput /></label> <label>${minLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=minInput /></label>
<label>${maxLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=maxInput /></label> <label>${maxLabel}<input min="${minHTML}" max="${maxHTML}" step=any type="${inputType}" data-ref=maxInput /></label>
</fieldset> </fieldset>
`) `
this.container = container }
this.minInput = minInput
this.maxInput = maxInput build() {
this.parentNode.appendChild(this.container) this.minInput = this.elements.minInput
this.maxInput = this.elements.maxInput
const { min, max, type } = this.properties.criteria
const { min: modifiedMin, max: modifiedMax } = this.get()
const currentMin = modifiedMin !== undefined ? modifiedMin : min
const currentMax = modifiedMax !== undefined ? modifiedMax : max
if (min != null) { if (min != null) {
// The value stored using setAttribute is not modified by // The value stored using setAttribute is not modified by
// user input, and will be used as initial value when calling // user input, and will be used as initial value when calling
@ -1061,6 +1052,7 @@ Fields.MinMaxBase = class extends Fields.FacetSearchBase {
this.minInput.addEventListener('change', () => this.sync()) this.minInput.addEventListener('change', () => this.sync())
this.maxInput.addEventListener('change', () => this.sync()) this.maxInput.addEventListener('change', () => this.sync())
super.build()
} }
toggleStatus() { toggleStatus() {
@ -1174,16 +1166,17 @@ Fields.MultiChoice = class extends BaseElement {
return this.properties.choices || this.choices return this.properties.choices || this.choices
} }
getTemplate() {
return `<div class="${this.getClassName()} by${this.getChoices().length}" data-ref=wrapper></div>`
}
build() { build() {
const choices = this.getChoices() const choices = this.getChoices()
this.container = Utils.loadTemplate(
`<div class="${this.getClassName()} by${choices.length}"></div>`
)
this.parentNode.appendChild(this.container)
for (const [i, [value, label]] of choices.entries()) { for (const [i, [value, label]] of choices.entries()) {
this.addChoice(value, label, i) this.addChoice(value, label, i)
} }
this.fetch() this.fetch()
super.build()
} }
addChoice(value, label, counter) { addChoice(value, label, counter) {
@ -1191,8 +1184,8 @@ Fields.MultiChoice = class extends BaseElement {
const input = Utils.loadTemplate( const input = Utils.loadTemplate(
`<input type="radio" name="${this.name}" id="${id}" value="${value}" />` `<input type="radio" name="${this.name}" id="${id}" value="${value}" />`
) )
this.container.appendChild(input) this.elements.wrapper.appendChild(input)
this.container.appendChild( this.elements.wrapper.appendChild(
Utils.loadTemplate(`<label for="${id}">${label}</label>`) Utils.loadTemplate(`<label for="${id}">${label}</label>`)
) )
input.addEventListener('change', () => this.sync()) input.addEventListener('change', () => this.sync())
@ -1259,10 +1252,11 @@ Fields.Range = class extends Fields.FloatInput {
} }
value() { value() {
return this.wrapper.classList.contains('undefined') ? undefined : super.value() return this.root.classList.contains('undefined') ? undefined : super.value()
} }
buildHelpText() { build() {
super.build()
let options = '' let options = ''
const step = this.properties.step || 1 const step = this.properties.step || 1
const digits = step < 1 ? 1 : 0 const digits = step < 1 ? 1 : 0
@ -1272,16 +1266,14 @@ Fields.Range = class extends Fields.FloatInput {
i <= this.properties.max; i <= this.properties.max;
i += this.properties.step i += this.properties.step
) { ) {
options += `<option value="${i.toFixed(digits)}" label="${i.toFixed( const ii = i.toFixed(digits)
digits options += `<option value="${ii}" label="${ii}"></option>`
)}"></option>`
} }
const parent = this.getHelpTextParent()
const datalist = Utils.loadTemplate( const datalist = Utils.loadTemplate(
`<datalist class="umap-field-datalist" id="${id}">${options}</datalist>` `<datalist class="umap-field-datalist" id="${id}">${options}</datalist>`
) )
this.container.appendChild(datalist)
this.input.setAttribute('list', id) this.input.setAttribute('list', id)
super.buildHelpText()
} }
} }
@ -1292,12 +1284,13 @@ Fields.ManageOwner = class extends BaseElement {
on_select: L.bind(this.onSelect, this), on_select: L.bind(this.onSelect, this),
placeholder: translate("Type new owner's username"), placeholder: translate("Type new owner's username"),
} }
this.autocomplete = new AjaxAutocomplete(this.parentNode, options) this.autocomplete = new AjaxAutocomplete(this.container, options)
const owner = this.toHTML() const owner = this.toHTML()
if (owner) if (owner) {
this.autocomplete.displaySelected({ this.autocomplete.displaySelected({
item: { value: owner.id, label: owner.name }, item: { value: owner.id, label: owner.name },
}) })
}
} }
value() { value() {
@ -1322,7 +1315,7 @@ Fields.ManageEditors = class extends BaseElement {
on_unselect: L.bind(this.onUnselect, this), on_unselect: L.bind(this.onUnselect, this),
placeholder: translate("Type editor's username"), placeholder: translate("Type editor's username"),
} }
this.autocomplete = new AjaxAutocompleteMultiple(this.parentNode, options) this.autocomplete = new AjaxAutocompleteMultiple(this.container, options)
this._values = this.toHTML() this._values = this.toHTML()
if (this._values) if (this._values)
for (let i = 0; i < this._values.length; i++) for (let i = 0; i < this._values.length; i++)

View file

@ -228,7 +228,9 @@ export default class Help {
parse(container) { parse(container) {
for (const element of container.querySelectorAll('[data-help]')) { for (const element of container.querySelectorAll('[data-help]')) {
this.button(element, element.dataset.help.split(',')) if (element.dataset.help) {
this.button(element, element.dataset.help.split(','))
}
} }
} }

View file

@ -416,9 +416,11 @@ export function loadTemplate(html) {
} }
export function loadTemplateWithRefs(html) { export function loadTemplateWithRefs(html) {
const element = loadTemplate(html) const template = document.createElement('template')
template.innerHTML = html
const element = template.content.firstElementChild
const elements = {} const elements = {}
for (const node of element.querySelectorAll('[data-ref]')) { for (const node of template.content.querySelectorAll('[data-ref]')) {
elements[node.dataset.ref] = node elements[node.dataset.ref] = node
} }
return [element, elements] return [element, elements]

View file

@ -101,7 +101,7 @@ def test_can_remove_stroke(live_server, openmap, page, bootstrap):
page.get_by_role("link", name="Toggle edit mode").click() page.get_by_role("link", name="Toggle edit mode").click()
page.get_by_text("Shape properties").click() page.get_by_text("Shape properties").click()
page.locator(".umap-field-stroke .define").first.click() page.locator(".umap-field-stroke .define").first.click()
page.locator(".umap-field-stroke label").first.click() page.locator(".umap-field-stroke .show-on-defined label").first.click()
expect(page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']")).to_have_count( expect(page.locator(".leaflet-overlay-pane path[stroke='DarkBlue']")).to_have_count(
0 0
) )

View file

@ -187,9 +187,11 @@ def test_websocket_connection_can_sync_map_properties(
# Zoom control is synced # Zoom control is synced
peerB.get_by_role("link", name="Map advanced properties").click() peerB.get_by_role("link", name="Map advanced properties").click()
peerB.locator("summary").filter(has_text="User interface options").click() peerB.locator("summary").filter(has_text="User interface options").click()
peerB.locator("div").filter( switch = peerB.locator("div.formbox").filter(
has_text=re.compile(r"^Display the zoom control") has_text=re.compile("Display the zoom control")
).locator("label").nth(2).click() )
expect(switch).to_be_visible()
switch.get_by_text("Never").click()
expect(peerA.locator(".leaflet-control-zoom")).to_be_hidden() expect(peerA.locator(".leaflet-control-zoom")).to_be_hidden()