diff --git a/package.json b/package.json
index b20caee1..adf552b6 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,6 @@
"leaflet": "1.9.4",
"leaflet-editable": "^1.3.0",
"leaflet-editinosm": "0.2.3",
- "leaflet-formbuilder": "0.2.10",
"leaflet-fullscreen": "1.0.2",
"leaflet-hash": "0.2.1",
"leaflet-i18n": "0.3.5",
diff --git a/scripts/vendorsjs.sh b/scripts/vendorsjs.sh
index ca722a2f..7508460c 100755
--- a/scripts/vendorsjs.sh
+++ b/scripts/vendorsjs.sh
@@ -17,7 +17,6 @@ mkdir -p umap/static/umap/vendors/markercluster/ && cp -r node_modules/leaflet.m
mkdir -p umap/static/umap/vendors/heat/ && cp -r node_modules/leaflet.heat/dist/leaflet-heat.js umap/static/umap/vendors/heat/
mkdir -p umap/static/umap/vendors/fullscreen/ && cp -r node_modules/leaflet-fullscreen/dist/** umap/static/umap/vendors/fullscreen/
mkdir -p umap/static/umap/vendors/toolbar/ && cp -r node_modules/leaflet-toolbar/dist/leaflet.toolbar.* umap/static/umap/vendors/toolbar/
-mkdir -p umap/static/umap/vendors/formbuilder/ && cp -r node_modules/leaflet-formbuilder/Leaflet.FormBuilder.js umap/static/umap/vendors/formbuilder/
mkdir -p umap/static/umap/vendors/measurable/ && cp -r node_modules/leaflet-measurable/Leaflet.Measurable.* umap/static/umap/vendors/measurable/
mkdir -p umap/static/umap/vendors/photon/ && cp -r node_modules/leaflet.photon/leaflet.photon.js umap/static/umap/vendors/photon/
mkdir -p umap/static/umap/vendors/csv2geojson/ && cp -r node_modules/csv2geojson/csv2geojson.js umap/static/umap/vendors/csv2geojson/
diff --git a/umap/static/umap/js/modules/browser.js b/umap/static/umap/js/modules/browser.js
index 3faef4cc..01b20ebb 100644
--- a/umap/static/umap/js/modules/browser.js
+++ b/umap/static/umap/js/modules/browser.js
@@ -4,6 +4,7 @@ import * as Icon from './rendering/icon.js'
import * as Utils from './utils.js'
import { EXPORT_FORMATS } from './formatter.js'
import ContextMenu from './ui/contextmenu.js'
+import { Form } from './form/builder.js'
export default class Browser {
constructor(umap, leafletMap) {
@@ -179,7 +180,7 @@ export default class Browser {
],
['options.inBbox', { handler: 'Switch', label: translate('Current map view') }],
]
- const builder = new L.FormBuilder(this, fields, {
+ const builder = new Form(this, fields, {
callback: () => this.onFormChange(),
})
let filtersBuilder
@@ -189,7 +190,7 @@ export default class Browser {
})
if (this._umap.properties.facetKey) {
fields = this._umap.facets.build()
- filtersBuilder = new L.FormBuilder(this._umap.facets, fields, {
+ filtersBuilder = new Form(this._umap.facets, fields, {
callback: () => this.onFormChange(),
})
DomEvent.on(filtersBuilder.form, 'reset', () => {
diff --git a/umap/static/umap/js/modules/form/builder.js b/umap/static/umap/js/modules/form/builder.js
new file mode 100644
index 00000000..86a7ac84
--- /dev/null
+++ b/umap/static/umap/js/modules/form/builder.js
@@ -0,0 +1,209 @@
+import getClass from './fields.js'
+import * as Utils from '../utils.js'
+import { SCHEMA } from '../schema.js'
+
+export class Form {
+ constructor(obj, fields, properties) {
+ this.setProperties(properties)
+ this.defaultProperties = {}
+ this.obj = obj
+ this.form = Utils.loadTemplate('
')
+ this.setFields(fields)
+ if (this.properties.id) {
+ this.form.id = this.properties.id
+ }
+ if (this.properties.className) {
+ this.form.classList.add(this.properties.className)
+ }
+ }
+
+ setProperties(properties) {
+ this.properties = Object.assign({}, this.properties, properties)
+ }
+
+ setFields(fields) {
+ this.fields = fields || []
+ this.helpers = {}
+ }
+
+ build() {
+ this.form.innerHTML = ''
+ for (const definition of this.fields) {
+ this.buildField(this.makeField(definition))
+ }
+ return this.form
+ }
+
+ buildField(field) {
+ field.buildLabel()
+ field.build()
+ field.buildHelpText()
+ }
+
+ makeField(field) {
+ // field can be either a string like "option.name" or a full definition array,
+ // like ['properties.tilelayer.tms', {handler: 'CheckBox', helpText: 'TMS format'}]
+ let properties
+ if (Array.isArray(field)) {
+ properties = field[1] || {}
+ field = field[0]
+ } else {
+ properties = this.defaultProperties[this.getName(field)] || {}
+ }
+ const class_ = getClass(properties.handler || 'Input')
+ this.helpers[field] = new class_(this, field, properties)
+ return this.helpers[field]
+ }
+
+ getter(field) {
+ const path = field.split('.')
+ let value = this.obj
+ for (const sub of path) {
+ try {
+ value = value[sub]
+ } catch {
+ console.log(field)
+ }
+ }
+ return value
+ }
+
+ setter(field, value) {
+ const path = field.split('.')
+ let obj = this.obj
+ let what
+ for (let i = 0, l = path.length; i < l; i++) {
+ what = path[i]
+ if (what === path[l - 1]) {
+ if (typeof value === 'undefined') {
+ delete obj[what]
+ } else {
+ obj[what] = value
+ }
+ } else {
+ obj = obj[what]
+ }
+ }
+ }
+
+ restoreField(field) {
+ const initial = this.helpers[field].initial
+ this.setter(field, initial)
+ }
+
+ getName(field) {
+ const fieldEls = field.split('.')
+ return fieldEls[fieldEls.length - 1]
+ }
+
+ fetchAll() {
+ for (const helper of Object.values(this.helpers)) {
+ helper.fetch()
+ }
+ }
+
+ syncAll() {
+ for (const helper of Object.values(this.helpers)) {
+ helper.sync()
+ }
+ }
+
+ onPostSync() {
+ if (this.properties.callback) {
+ this.properties.callback(this.obj)
+ }
+ }
+}
+
+export class DataForm extends Form {
+ constructor(obj, fields, properties) {
+ super(obj, fields, properties)
+ this._umap = obj._umap || properties.umap
+ this.computeDefaultProperties()
+ // this.on('finish', this.finish)
+ }
+
+ computeDefaultProperties() {
+ const customHandlers = {
+ sortKey: 'PropertyInput',
+ easing: 'Switch',
+ facetKey: 'PropertyInput',
+ slugKey: 'PropertyInput',
+ labelKey: 'PropertyInput',
+ }
+ for (const [key, schema] of Object.entries(SCHEMA)) {
+ if (schema.type === Boolean) {
+ if (schema.nullable) schema.handler = 'NullableChoices'
+ else schema.handler = 'Switch'
+ } else if (schema.type === 'Text') {
+ schema.handler = 'Textarea'
+ } else if (schema.type === Number) {
+ if (schema.step) schema.handler = 'Range'
+ else schema.handler = 'IntInput'
+ } else if (schema.choices) {
+ const text_length = schema.choices.reduce(
+ (acc, [_, label]) => acc + label.length,
+ 0
+ )
+ // Try to be smart and use MultiChoice only
+ // for choices where labels are shorts…
+ if (text_length < 40) {
+ schema.handler = 'MultiChoice'
+ } else {
+ schema.handler = 'Select'
+ schema.selectOptions = schema.choices
+ }
+ } else {
+ switch (key) {
+ case 'color':
+ case 'fillColor':
+ schema.handler = 'ColorPicker'
+ break
+ case 'iconUrl':
+ schema.handler = 'IconUrl'
+ break
+ case 'licence':
+ schema.handler = 'LicenceChooser'
+ break
+ }
+ }
+
+ if (customHandlers[key]) {
+ schema.handler = customHandlers[key]
+ }
+ // Input uses this key for its type attribute
+ delete schema.type
+ this.defaultProperties[key] = schema
+ }
+ }
+
+ setter(field, value) {
+ super.setter(field, value)
+ this.obj.isDirty = true
+ if ('render' in this.obj) {
+ this.obj.render([field], this)
+ }
+ if ('sync' in this.obj) {
+ this.obj.sync.update(field, value)
+ }
+ }
+
+ getter(field) {
+ const path = field.split('.')
+ let value = this.obj
+ let sub
+ for (sub of path) {
+ try {
+ value = value[sub]
+ } catch {
+ console.log(field)
+ }
+ }
+ if (value === undefined) value = SCHEMA[sub]?.default
+ return value
+ }
+
+ finish(event) {
+ event.helper?.input?.blur()
+ }
+}
diff --git a/umap/static/umap/js/modules/form/fields.js b/umap/static/umap/js/modules/form/fields.js
new file mode 100644
index 00000000..9a4fe867
--- /dev/null
+++ b/umap/static/umap/js/modules/form/fields.js
@@ -0,0 +1,1361 @@
+import * as Utils from '../utils.js'
+import { translate } from '../i18n.js'
+import {
+ AjaxAutocomplete,
+ AjaxAutocompleteMultiple,
+ AutocompleteDatalist,
+} from '../autocomplete.js'
+
+const Fields = {}
+
+export default function getClass(name) {
+ if (typeof name === 'function') return name
+ if (!Fields[name]) throw Error(`Unknown class ${name}`)
+ return Fields[name]
+}
+
+class BaseElement {
+ constructor(builder, field, properties) {
+ this.builder = builder
+ this.obj = this.builder.obj
+ this.form = this.builder.form
+ this.field = field
+ this.setProperties(properties)
+ this.fieldEls = this.field.split('.')
+ this.name = this.builder.getName(field)
+ this.parentNode = this.getParentNode()
+ }
+
+ setProperties(properties) {
+ this.properties = Object.assign({}, this.properties, properties)
+ }
+
+ onDefine() {}
+
+ getParentNode() {
+ const classNames = ['formbox']
+ if (this.properties.inheritable) {
+ classNames.push(inheritable)
+ if (this.get(true)) classNames.push('undefined')
+ }
+ classNames.push(`umap-field-${this.name}`)
+ const [wrapper, { header, define, undefine, quickContainer, container }] =
+ Utils.loadTemplateWithRefs(`
+ `)
+ this.wrapper = wrapper
+ this.wrapper.classList.add(...classNames)
+ this.header = header
+ this.form.appendChild(this.wrapper)
+ if (this.properties.inheritable) {
+ define.addEventListener('click', (event) => {
+ e.preventDefault()
+ e.stopPropagation()
+ this.fetch()
+ this.onDefine()
+ this.wrapper.classList.remove('undefined')
+ })
+ undefine.addEventListener('click', () => this.undefine())
+ } else {
+ define.hidden = true
+ undefine.hidden = true
+ }
+
+ this.quickContainer = quickContainer
+ this.extendedContainer = container
+ return this.extendedContainer
+ }
+
+ clear() {
+ this.input.value = ''
+ }
+
+ get(own) {
+ if (!this.properties.inheritable || own) return this.builder.getter(this.field)
+ const path = this.field.split('.')
+ const key = path[path.length - 1]
+ return this.obj.getOption(key)
+ }
+
+ toHTML() {
+ return this.get()
+ }
+
+ toJS() {
+ return this.value()
+ }
+
+ sync() {
+ this.set()
+ this.onPostSync()
+ }
+
+ set() {
+ this.builder.setter(this.field, this.toJS())
+ }
+
+ getLabelParent() {
+ return this.header
+ }
+
+ getHelpTextParent() {
+ return this.parentNode
+ }
+
+ buildLabel() {
+ if (this.properties.label) {
+ this.label = L.DomUtil.create('label', '', this.getLabelParent())
+ this.label.textContent = this.label.title = this.properties.label
+ if (this.properties.helpEntries) {
+ this.builder._umap.help.button(this.label, this.properties.helpEntries)
+ } else if (this.properties.helpTooltip) {
+ const info = L.DomUtil.create('i', 'info', this.label)
+ L.DomEvent.on(info, 'mouseover', () => {
+ this.builder._umap.tooltip.open({
+ anchor: info,
+ content: this.properties.helpTooltip,
+ position: 'top',
+ })
+ })
+ }
+ }
+ }
+
+ buildHelpText() {
+ if (this.properties.helpText) {
+ const container = L.DomUtil.create('small', 'help-text', this.getHelpTextParent())
+ container.innerHTML = this.properties.helpText
+ }
+ }
+
+ fetch() {}
+
+ finish() {
+ this.fireAndForward('finish')
+ }
+
+ onPostSync() {
+ if (this.properties.callback) {
+ this.properties.callback(this.obj)
+ }
+ this.builder.onPostSync()
+ }
+
+ undefine() {
+ this.wrapper.classList.add('undefined')
+ this.clear()
+ this.sync()
+ }
+}
+
+Fields.Textarea = class extends BaseElement {
+ build() {
+ this.input = L.DomUtil.create(
+ 'textarea',
+ this.properties.className || '',
+ this.parentNode
+ )
+ if (this.properties.placeholder)
+ this.input.placeholder = this.properties.placeholder
+ this.fetch()
+ L.DomEvent.on(this.input, 'input', this.sync, this)
+ L.DomEvent.on(this.input, 'keypress', this.onKeyPress, this)
+ }
+
+ fetch() {
+ const value = this.toHTML()
+ this.initial = value
+ if (value) {
+ this.input.value = value
+ }
+ }
+
+ value() {
+ return this.input.value
+ }
+
+ onKeyPress(e) {
+ if (e.key === 'Enter' && (e.shiftKey || e.ctrlKey)) {
+ L.DomEvent.stop(e)
+ this.finish()
+ }
+ }
+}
+
+Fields.Input = class extends BaseElement {
+ build() {
+ this.input = L.DomUtil.create(
+ 'input',
+ this.properties.className || '',
+ this.parentNode
+ )
+ this.input.type = this.type()
+ this.input.name = this.name
+ this.input._helper = this
+ if (this.properties.placeholder) {
+ this.input.placeholder = this.properties.placeholder
+ }
+ if (this.properties.min !== undefined) {
+ this.input.min = this.properties.min
+ }
+ if (this.properties.max !== undefined) {
+ this.input.max = this.properties.max
+ }
+ if (this.properties.step) {
+ this.input.step = this.properties.step
+ }
+ this.fetch()
+ L.DomEvent.on(this.input, this.getSyncEvent(), this.sync, this)
+ L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
+ }
+
+ fetch() {
+ const value = this.toHTML() !== undefined ? this.toHTML() : null
+ this.initial = value
+ this.input.value = value
+ }
+
+ getSyncEvent() {
+ return 'input'
+ }
+
+ type() {
+ return this.properties.type || 'text'
+ }
+
+ value() {
+ return this.input.value || undefined
+ }
+
+ onKeyDown(e) {
+ if (e.key === 'Enter') {
+ L.DomEvent.stop(e)
+ this.finish()
+ }
+ }
+}
+
+Fields.BlurInput = class extends Fields.Input {
+ getSyncEvent() {
+ return 'blur'
+ }
+
+ build() {
+ this.properties.className = 'blur'
+ super.build()
+ const button = L.DomUtil.create('span', 'button blur-button')
+ L.DomUtil.after(this.input, button)
+ this.input.addEventListener('focus', () => this.fetch())
+ }
+
+ finish() {
+ this.sync()
+ super.finish()
+ }
+
+ sync() {
+ // Do not commit any change if user only clicked
+ // on the field than clicked outside
+ if (this.initial !== this.value()) {
+ super.sync()
+ }
+ }
+}
+const IntegerMixin = (Base) =>
+ class extends Base {
+ value() {
+ return !isNaN(this.input.value) && this.input.value !== ''
+ ? parseInt(this.input.value, 10)
+ : undefined
+ }
+
+ type() {
+ return 'number'
+ }
+ }
+
+Fields.IntInput = class extends IntegerMixin(Fields.Input) {}
+Fields.BlurIntInput = class extends IntegerMixin(Fields.BlurInput) {}
+
+const FloatMixin = (Base) =>
+ class extends Base {
+ value() {
+ return !isNaN(this.input.value) && this.input.value !== ''
+ ? parseFloat(this.input.value)
+ : undefined
+ }
+
+ type() {
+ return 'number'
+ }
+ }
+
+Fields.FloatInput = class extends FloatMixin(Fields.Input) {
+ // options: {
+ // step: 'any',
+ // }
+}
+
+Fields.BlurFloatInput = class extends FloatMixin(Fields.BlurInput) {
+ // options: {
+ // step: 'any',
+ // },
+}
+
+Fields.CheckBox = class extends BaseElement {
+ build() {
+ const container = Utils.loadTemplate('')
+ this.parentNode.appendChild(container)
+ this.input = L.DomUtil.create('input', this.properties.className || '', container)
+ this.input.type = 'checkbox'
+ this.input.name = this.name
+ this.input._helper = this
+ this.fetch()
+ this.input.addEventListener('change', () => this.sync())
+ }
+
+ fetch() {
+ this.initial = this.toHTML()
+ this.input.checked = this.initial === true
+ }
+
+ value() {
+ return this.wrapper.classList.contains('undefined') ? undefined : this.input.checked
+ }
+
+ toHTML() {
+ return [1, true].indexOf(this.get()) !== -1
+ }
+
+ clear() {
+ this.fetch()
+ }
+}
+
+Fields.Select = class extends BaseElement {
+ build() {
+ this.select = L.DomUtil.create('select', '', this.parentNode)
+ this.select.name = this.name
+ this.validValues = []
+ this.buildOptions()
+ L.DomEvent.on(this.select, 'change', this.sync, this)
+ }
+
+ getOptions() {
+ return this.properties.selectOptions
+ }
+
+ fetch() {
+ this.buildOptions()
+ }
+
+ buildOptions() {
+ this.select.innerHTML = ''
+ for (const option of this.getOptions()) {
+ if (typeof option === 'string') this.buildOption(option, option)
+ else this.buildOption(option[0], option[1])
+ }
+ }
+
+ buildOption(value, label) {
+ this.validValues.push(value)
+ const option = L.DomUtil.create('option', '', this.select)
+ option.value = value
+ option.innerHTML = label
+ if (this.toHTML() === value) {
+ option.selected = 'selected'
+ }
+ }
+
+ value() {
+ if (this.select[this.select.selectedIndex])
+ return this.select[this.select.selectedIndex].value
+ }
+
+ getDefault() {
+ if (this.properties.inheritable) return undefined
+ return this.getOptions()[0][0]
+ }
+
+ toJS() {
+ const value = this.value()
+ if (this.validValues.indexOf(value) !== -1) {
+ return value
+ }
+ return this.getDefault()
+ }
+
+ clear() {
+ this.select.value = ''
+ }
+}
+
+Fields.IntSelect = class extends Fields.Select {
+ value() {
+ return parseInt(super.value(), 10)
+ }
+}
+
+Fields.NullableBoolean = class extends Fields.Select {
+ getOptions() {
+ return [
+ [undefined, 'inherit'],
+ [true, 'yes'],
+ [false, 'no'],
+ ]
+ }
+
+ toJS() {
+ let value = this.value()
+ switch (value) {
+ case 'true':
+ case true:
+ value = true
+ break
+ case 'false':
+ case false:
+ value = false
+ break
+ default:
+ value = undefined
+ }
+ return value
+ }
+}
+
+Fields.EditableText = class extends BaseElement {
+ build() {
+ this.input = L.DomUtil.create(
+ 'span',
+ this.properties.className || '',
+ this.parentNode
+ )
+ this.input.contentEditable = true
+ this.fetch()
+ L.DomEvent.on(this.input, 'input', this.sync, this)
+ L.DomEvent.on(this.input, 'keypress', this.onKeyPress, this)
+ }
+
+ getParentNode() {
+ return this.form
+ }
+
+ value() {
+ return this.input.textContent
+ }
+
+ fetch() {
+ this.input.textContent = this.toHTML()
+ }
+
+ onKeyPress(event) {
+ if (event.keyCode === 13) {
+ event.preventDefault()
+ this.input.blur()
+ }
+ }
+}
+
+Fields.ColorPicker = class extends Fields.Input {
+ getColors() {
+ return Utils.COLORS
+ }
+
+ getParentNode() {
+ super.getParentNode()
+ return this.quickContainer
+ }
+
+ build() {
+ super.build()
+ this.input.placeholder = this.properties.placeholder || translate('Inherit')
+ this.container = L.DomUtil.create(
+ 'div',
+ 'umap-color-picker',
+ this.extendedContainer
+ )
+ this.container.style.display = 'none'
+ for (const idx in this.colors) {
+ this.addColor(this.colors[idx])
+ }
+ this.spreadColor()
+ this.input.autocomplete = 'off'
+ L.DomEvent.on(this.input, 'focus', this.onFocus, this)
+ L.DomEvent.on(this.input, 'blur', this.onBlur, this)
+ L.DomEvent.on(this.input, 'change', this.sync, this)
+ this.on('define', this.onFocus)
+ }
+
+ onFocus() {
+ this.container.style.display = 'block'
+ this.spreadColor()
+ }
+
+ onBlur() {
+ const closePicker = () => {
+ this.container.style.display = 'none'
+ }
+ // We must leave time for the click to be listened.
+ window.setTimeout(closePicker, 100)
+ }
+
+ sync() {
+ this.spreadColor()
+ super.sync()
+ }
+
+ spreadColor() {
+ if (this.input.value) this.input.style.backgroundColor = this.input.value
+ else this.input.style.backgroundColor = 'inherit'
+ }
+
+ addColor(colorName) {
+ const span = L.DomUtil.create('span', '', this.container)
+ span.style.backgroundColor = span.title = colorName
+ const updateColorInput = function () {
+ this.input.value = colorName
+ this.sync()
+ this.container.style.display = 'none'
+ }
+ L.DomEvent.on(span, 'mousedown', updateColorInput, this)
+ }
+}
+
+Fields.TextColorPicker = class extends Fields.ColorPicker {
+ getColors() {
+ return [
+ 'Black',
+ 'DarkSlateGrey',
+ 'DimGrey',
+ 'SlateGrey',
+ 'LightSlateGrey',
+ 'Grey',
+ 'DarkGrey',
+ 'LightGrey',
+ 'White',
+ ]
+ }
+}
+
+Fields.LayerTypeChooser = class extends Fields.Select {
+ getOptions() {
+ return U.LAYER_TYPES.map((class_) => [class_.TYPE, class_.NAME])
+ }
+}
+
+Fields.SlideshowDelay = class extends Fields.IntSelect {
+ getOptions() {
+ const options = []
+ for (let i = 1; i < 30; i++) {
+ options.push([i * 1000, translate('{delay} seconds', { delay: i })])
+ }
+ return options
+ }
+}
+
+Fields.DataLayerSwitcher = class extends Fields.Select {
+ getOptions() {
+ const options = []
+ this.builder._umap.eachDataLayerReverse((datalayer) => {
+ if (
+ datalayer.isLoaded() &&
+ !datalayer.isDataReadOnly() &&
+ datalayer.isBrowsable()
+ ) {
+ options.push([L.stamp(datalayer), datalayer.getName()])
+ }
+ })
+ return options
+ }
+
+ toHTML() {
+ return L.stamp(this.obj.datalayer)
+ }
+
+ toJS() {
+ return this.builder._umap.datalayers[this.value()]
+ }
+
+ set() {
+ this.builder._umap.lastUsedDataLayer = this.toJS()
+ this.obj.changeDataLayer(this.toJS())
+ }
+}
+
+Fields.DataFormat = class extends Fields.Select {
+ getOptions() {
+ return [
+ [undefined, translate('Choose the data format')],
+ ['geojson', 'geojson'],
+ ['osm', 'osm'],
+ ['csv', 'csv'],
+ ['gpx', 'gpx'],
+ ['kml', 'kml'],
+ ['georss', 'georss'],
+ ]
+ }
+}
+
+Fields.LicenceChooser = class extends Fields.Select {
+ getOptions() {
+ const licences = []
+ const licencesList = this.builder.obj.properties.licences
+ let licence
+ for (const i in licencesList) {
+ licence = licencesList[i]
+ licences.push([i, licence.name])
+ }
+ return licences
+ }
+
+ toHTML() {
+ return this.get()?.name
+ }
+
+ toJS() {
+ return this.builder.obj.properties.licences[this.value()]
+ }
+}
+
+Fields.NullableBoolean = class extends Fields.Select {
+ getOptions() {
+ return [
+ [undefined, translate('inherit')],
+ [true, translate('yes')],
+ [false, translate('no')],
+ ]
+ }
+
+ toJS() {
+ let value = this.value()
+ switch (value) {
+ case 'true':
+ case true:
+ value = true
+ break
+ case 'false':
+ case false:
+ value = false
+ break
+ default:
+ value = undefined
+ }
+ return value
+ }
+}
+
+// Adds an autocomplete using all available user defined properties
+Fields.PropertyInput = class extends Fields.BlurInput {
+ build() {
+ super.build()
+ const autocomplete = new AutocompleteDatalist(this.input)
+ // Will be used on Umap and DataLayer
+ const properties = this.builder.obj.allProperties()
+ autocomplete.suggestions = properties
+ }
+}
+
+Fields.IconUrl = class extends Fields.BlurInput {
+ type() {
+ return 'hidden'
+ }
+
+ build() {
+ super.build()
+ this.buttons = L.DomUtil.create('div', '', this.parentNode)
+ this.tabs = L.DomUtil.create('div', 'flat-tabs', this.parentNode)
+ this.body = L.DomUtil.create('div', 'umap-pictogram-body', this.parentNode)
+ this.footer = L.DomUtil.create('div', '', this.parentNode)
+ this.updatePreview()
+ this.on('define', this.onDefine)
+ }
+
+ async onDefine() {
+ this.buttons.innerHTML = ''
+ this.footer.innerHTML = ''
+ const [{ pictogram_list }, response, error] = await this.builder._umap.server.get(
+ this.builder._umap.properties.urls.pictogram_list_json
+ )
+ if (!error) this.pictogram_list = pictogram_list
+ this.buildTabs()
+ const value = this.value()
+ if (U.Icon.RECENT.length) this.showRecentTab()
+ else if (!value || Utils.isPath(value)) this.showSymbolsTab()
+ else if (Utils.isRemoteUrl(value) || Utils.isDataImage(value)) this.showURLTab()
+ else this.showCharsTab()
+ const closeButton = L.DomUtil.createButton(
+ 'button action-button',
+ this.footer,
+ translate('Close'),
+ function (e) {
+ this.body.innerHTML = ''
+ this.tabs.innerHTML = ''
+ this.footer.innerHTML = ''
+ if (this.isDefault()) this.undefine(e)
+ else this.updatePreview()
+ },
+ this
+ )
+ }
+
+ buildTabs() {
+ this.tabs.innerHTML = ''
+ if (U.Icon.RECENT.length) {
+ const recent = L.DomUtil.add(
+ 'button',
+ 'flat tab-recent',
+ this.tabs,
+ translate('Recent')
+ )
+ L.DomEvent.on(recent, 'click', L.DomEvent.stop).on(
+ recent,
+ 'click',
+ this.showRecentTab,
+ this
+ )
+ }
+ const symbol = L.DomUtil.add(
+ 'button',
+ 'flat tab-symbols',
+ this.tabs,
+ translate('Symbol')
+ )
+ const char = L.DomUtil.add(
+ 'button',
+ 'flat tab-chars',
+ this.tabs,
+ translate('Emoji & Character')
+ )
+ url = L.DomUtil.add('button', 'flat tab-url', this.tabs, translate('URL'))
+ L.DomEvent.on(symbol, 'click', L.DomEvent.stop).on(
+ symbol,
+ 'click',
+ this.showSymbolsTab,
+ this
+ )
+ L.DomEvent.on(char, 'click', L.DomEvent.stop).on(
+ char,
+ 'click',
+ this.showCharsTab,
+ this
+ )
+ L.DomEvent.on(url, 'click', L.DomEvent.stop).on(url, 'click', this.showURLTab, this)
+ }
+
+ openTab(name) {
+ const els = this.tabs.querySelectorAll('button')
+ for (const el of els) {
+ L.DomUtil.removeClass(el, 'on')
+ }
+ const el = this.tabs.querySelector(`.tab-${name}`)
+ L.DomUtil.addClass(el, 'on')
+ this.body.innerHTML = ''
+ }
+
+ updatePreview() {
+ this.buttons.innerHTML = ''
+ if (this.isDefault()) return
+ if (!Utils.hasVar(this.value())) {
+ // Do not try to render URL with variables
+ const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
+ L.DomEvent.on(box, 'click', this.onDefine, this)
+ const icon = U.Icon.makeElement(this.value(), box)
+ }
+ this.button = L.DomUtil.createButton(
+ 'button action-button',
+ this.buttons,
+ this.value() ? translate('Change') : translate('Add'),
+ this.onDefine,
+ this
+ )
+ }
+
+ addIconPreview(pictogram, parent) {
+ const baseClass = 'umap-pictogram-choice'
+ const value = pictogram.src
+ const search = Utils.normalize(this.searchInput.value)
+ const title = pictogram.attribution
+ ? `${pictogram.name} — © ${pictogram.attribution}`
+ : pictogram.name || pictogram.src
+ if (search && Utils.normalize(title).indexOf(search) === -1) return
+ const className = value === this.value() ? `${baseClass} selected` : baseClass
+ const container = L.DomUtil.create('div', className, parent)
+ U.Icon.makeElement(value, container)
+ container.title = title
+ L.DomEvent.on(
+ container,
+ 'click',
+ function (e) {
+ this.input.value = value
+ this.sync()
+ this.unselectAll(this.grid)
+ L.DomUtil.addClass(container, 'selected')
+ },
+ this
+ )
+ return true // Icon has been added (not filtered)
+ }
+
+ clear() {
+ this.input.value = ''
+ this.unselectAll(this.body)
+ this.sync()
+ this.body.innerHTML = ''
+ this.updatePreview()
+ }
+
+ addCategory(items, name) {
+ const parent = L.DomUtil.create('div', 'umap-pictogram-category')
+ if (name) L.DomUtil.add('h6', '', parent, name)
+ const grid = L.DomUtil.create('div', 'umap-pictogram-grid', parent)
+ let status = false
+ for (const item of items) {
+ status = this.addIconPreview(item, grid) || status
+ }
+ if (status) this.grid.appendChild(parent)
+ }
+
+ buildSymbolsList() {
+ this.grid.innerHTML = ''
+ const categories = {}
+ let category
+ for (const props of this.pictogram_list) {
+ category = props.category || translate('Generic')
+ categories[category] = categories[category] || []
+ categories[category].push(props)
+ }
+ const sorted = Object.entries(categories).toSorted(([a], [b]) =>
+ Utils.naturalSort(a, b, U.lang)
+ )
+ for (const [name, items] of sorted) {
+ this.addCategory(items, name)
+ }
+ }
+
+ buildRecentList() {
+ this.grid.innerHTML = ''
+ const items = U.Icon.RECENT.map((src) => ({
+ src,
+ }))
+ this.addCategory(items)
+ }
+
+ isDefault() {
+ return !this.value() || this.value() === U.SCHEMA.iconUrl.default
+ }
+
+ addGrid(onSearch) {
+ this.searchInput = L.DomUtil.create('input', '', this.body)
+ this.searchInput.type = 'search'
+ this.searchInput.placeholder = translate('Search')
+ this.grid = L.DomUtil.create('div', '', this.body)
+ L.DomEvent.on(this.searchInput, 'input', onSearch, this)
+ }
+
+ showRecentTab() {
+ if (!U.Icon.RECENT.length) return
+ this.openTab('recent')
+ this.addGrid(this.buildRecentList)
+ this.buildRecentList()
+ }
+
+ showSymbolsTab() {
+ this.openTab('symbols')
+ this.addGrid(this.buildSymbolsList)
+ this.buildSymbolsList()
+ }
+
+ showCharsTab() {
+ this.openTab('chars')
+ const value = !U.Icon.isImg(this.value()) ? this.value() : null
+ const input = this.buildInput(this.body, value)
+ input.placeholder = translate('Type char or paste emoji')
+ input.type = 'text'
+ }
+
+ showURLTab() {
+ this.openTab('url')
+ const value =
+ Utils.isRemoteUrl(this.value()) || Utils.isDataImage(this.value())
+ ? this.value()
+ : null
+ const input = this.buildInput(this.body, value)
+ input.placeholder = translate('Add image URL')
+ input.type = 'url'
+ }
+
+ buildInput(parent, value) {
+ const input = L.DomUtil.create('input', 'blur', parent)
+ const button = L.DomUtil.create('span', 'button blur-button', parent)
+ if (value) input.value = value
+ L.DomEvent.on(input, 'blur', () => {
+ // Do not clear this.input when focus-blur
+ // empty input
+ if (input.value === value) return
+ this.input.value = input.value
+ this.sync()
+ })
+ return input
+ }
+
+ unselectAll(container) {
+ for (const el of container.querySelectorAll('div.selected')) {
+ el.classList.remove('selected')
+ }
+ }
+}
+
+Fields.Url = class extends Fields.Input {
+ type() {
+ return 'url'
+ }
+}
+
+Fields.Switch = class extends Fields.CheckBox {
+ getParentNode() {
+ super.getParentNode()
+ if (this.properties.inheritable) return this.quickContainer
+ return this.extendedContainer
+ }
+
+ build() {
+ super.build()
+ console.log(this)
+ if (this.properties.inheritable) {
+ this.label = Utils.loadTemplate('')
+ }
+ this.input.parentNode.appendChild(this.label)
+ L.DomUtil.addClass(this.input.parentNode, 'with-switch')
+ const id = `${this.builder.properties.id || Date.now()}.${this.name}`
+ this.label.setAttribute('for', id)
+ L.DomUtil.addClass(this.input, 'switch')
+ this.input.id = id
+ }
+}
+
+Fields.FacetSearchBase = class extends BaseElement {
+ buildLabel() {
+ this.label = L.DomUtil.element({
+ tagName: 'legend',
+ textContent: this.properties.label,
+ })
+ }
+}
+
+Fields.FacetSearchChoices = class extends Fields.FacetSearchBase {
+ build() {
+ this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
+ this.container.appendChild(this.label)
+ this.ul = L.DomUtil.create('ul', '', this.container)
+ this.type = this.properties.criteria.type
+
+ const choices = this.properties.criteria.choices
+ choices.sort()
+ choices.forEach((value) => this.buildLi(value))
+ }
+
+ buildLi(value) {
+ const property_li = L.DomUtil.create('li', '', this.ul)
+ const label = L.DomUtil.create('label', '', property_li)
+ const input = L.DomUtil.create('input', '', label)
+ L.DomUtil.add('span', '', label, value)
+
+ input.type = this.type
+ input.name = `${this.type}_${this.name}`
+ input.checked = this.get().choices.includes(value)
+ input.dataset.value = value
+
+ L.DomEvent.on(input, 'change', (e) => this.sync())
+ }
+
+ toJS() {
+ return {
+ type: this.type,
+ choices: [...this.ul.querySelectorAll('input:checked')].map(
+ (i) => i.dataset.value
+ ),
+ }
+ }
+}
+
+Fields.MinMaxBase = class extends Fields.FacetSearchBase {
+ getInputType(type) {
+ return type
+ }
+
+ getLabels() {
+ return [translate('Min'), translate('Max')]
+ }
+
+ prepareForHTML(value) {
+ return value.valueOf()
+ }
+
+ build() {
+ this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
+ this.container.appendChild(this.label)
+ 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.inputType = this.getInputType(this.type)
+
+ const [minLabel, maxLabel] = this.getLabels()
+
+ this.minLabel = L.DomUtil.create('label', '', this.container)
+ this.minLabel.textContent = minLabel
+
+ this.minInput = L.DomUtil.create('input', '', this.minLabel)
+ this.minInput.type = this.inputType
+ this.minInput.step = 'any'
+ this.minInput.min = this.prepareForHTML(min)
+ this.minInput.max = this.prepareForHTML(max)
+ if (min != null) {
+ // The value stored using setAttribute is not modified by
+ // user input, and will be used as initial value when calling
+ // form.reset(), and can also be retrieve later on by using
+ // getAttributing, to compare with current value and know
+ // if this value has been modified by the user
+ // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
+ this.minInput.setAttribute('value', this.prepareForHTML(min))
+ this.minInput.value = this.prepareForHTML(currentMin)
+ }
+
+ this.maxLabel = L.DomUtil.create('label', '', this.container)
+ this.maxLabel.textContent = maxLabel
+
+ this.maxInput = L.DomUtil.create('input', '', this.maxLabel)
+ this.maxInput.type = this.inputType
+ this.maxInput.step = 'any'
+ this.maxInput.min = this.prepareForHTML(min)
+ this.maxInput.max = this.prepareForHTML(max)
+ if (max != null) {
+ // Cf comment above about setAttribute vs value
+ this.maxInput.setAttribute('value', this.prepareForHTML(max))
+ this.maxInput.value = this.prepareForHTML(currentMax)
+ }
+ this.toggleStatus()
+
+ L.DomEvent.on(this.minInput, 'change', () => this.sync())
+ L.DomEvent.on(this.maxInput, 'change', () => this.sync())
+ }
+
+ toggleStatus() {
+ this.minInput.dataset.modified = this.isMinModified()
+ this.maxInput.dataset.modified = this.isMaxModified()
+ }
+
+ sync() {
+ super.sync()
+ this.toggleStatus()
+ }
+
+ isMinModified() {
+ const default_ = this.minInput.getAttribute('value')
+ const current = this.minInput.value
+ return current !== default_
+ }
+
+ isMaxModified() {
+ const default_ = this.maxInput.getAttribute('value')
+ const current = this.maxInput.value
+ return current !== default_
+ }
+
+ toJS() {
+ const opts = {
+ type: this.type,
+ }
+ if (this.minInput.value !== '' && this.isMinModified()) {
+ opts.min = this.prepareForJS(this.minInput.value)
+ }
+ if (this.maxInput.value !== '' && this.isMaxModified()) {
+ opts.max = this.prepareForJS(this.maxInput.value)
+ }
+ return opts
+ }
+}
+
+Fields.FacetSearchNumber = class extends Fields.MinMaxBase {
+ prepareForJS(value) {
+ return new Number(value)
+ }
+}
+
+Fields.FacetSearchDate = class extends Fields.MinMaxBase {
+ prepareForJS(value) {
+ return new Date(value)
+ }
+
+ toLocaleDateTime(dt) {
+ return new Date(dt.valueOf() - dt.getTimezoneOffset() * 60000)
+ }
+
+ prepareForHTML(value) {
+ // Value must be in local time
+ if (Number.isNaN(value)) return
+ return this.toLocaleDateTime(value).toISOString().substr(0, 10)
+ }
+
+ getLabels() {
+ return [translate('From'), translate('Until')]
+ }
+}
+
+Fields.FacetSearchDateTime = class extends Fields.FacetSearchDate {
+ getInputType(type) {
+ return 'datetime-local'
+ }
+
+ prepareForHTML(value) {
+ // Value must be in local time
+ if (Number.isNaN(value)) return
+ return this.toLocaleDateTime(value).toISOString().slice(0, -1)
+ }
+}
+
+Fields.MultiChoice = class extends BaseElement {
+ getDefault() {
+ return 'null'
+ }
+ getClassName() {
+ return 'umap-multiplechoice'
+ }
+
+ clear() {
+ const checked = this.container.querySelector('input[type="radio"]:checked')
+ if (checked) checked.checked = false
+ }
+
+ fetch() {
+ this.initial = this.toHTML()
+ let value = this.initial
+ if (!this.container.querySelector(`input[type="radio"][value="${value}"]`)) {
+ value =
+ this.properties.default !== undefined ? this.properties.default : this.default
+ }
+ const choices = this.getChoices().map(([value, label]) => `${value}`)
+ if (choices.includes(`${value}`)) {
+ this.container.querySelector(`input[type="radio"][value="${value}"]`).checked =
+ true
+ }
+ }
+
+ value() {
+ const checked = this.container.querySelector('input[type="radio"]:checked')
+ if (checked) return checked.value
+ }
+
+ getChoices() {
+ return this.properties.choices || this.choices
+ }
+
+ build() {
+ const choices = this.getChoices()
+ this.container = L.DomUtil.create(
+ 'div',
+ `${this.className} by${choices.length}`,
+ this.parentNode
+ )
+ for (const [i, [value, label]] of choices.entries()) {
+ this.addChoice(value, label, i)
+ }
+ this.fetch()
+ }
+
+ addChoice(value, label, counter) {
+ const input = L.DomUtil.create('input', '', this.container)
+ label = L.DomUtil.add('label', '', this.container, label)
+ input.type = 'radio'
+ input.name = this.name
+ input.value = value
+ const id = `${Date.now()}.${this.name}.${counter}`
+ label.setAttribute('for', id)
+ input.id = id
+ L.DomEvent.on(input, 'change', this.sync, this)
+ }
+}
+
+Fields.TernaryChoices = class extends Fields.MultiChoice {
+ getDefault() {
+ return 'null'
+ }
+
+ toJS() {
+ let value = this.value()
+ switch (value) {
+ case 'true':
+ case true:
+ value = true
+ break
+ case 'false':
+ case false:
+ value = false
+ break
+ case 'null':
+ case null:
+ value = null
+ break
+ default:
+ value = undefined
+ }
+ return value
+ }
+}
+
+Fields.NullableChoices = class extends Fields.TernaryChoices {
+ getChoices() {
+ return [
+ [true, translate('always')],
+ [false, translate('never')],
+ ['null', translate('hidden')],
+ ]
+ }
+}
+
+Fields.DataLayersControl = class extends Fields.TernaryChoices {
+ getChoices() {
+ return [
+ [true, translate('collapsed')],
+ ['expanded', translate('expanded')],
+ [false, translate('never')],
+ ['null', translate('hidden')],
+ ]
+ }
+
+ toJS() {
+ let value = this.value()
+ if (value !== 'expanded') value = super.toJS()
+ return value
+ }
+}
+
+Fields.Range = class extends Fields.FloatInput {
+ type() {
+ return 'range'
+ }
+
+ value() {
+ return this.wrapper.classList.contains('undefined') ? undefined : super.value()
+ }
+
+ buildHelpText() {
+ let options = ''
+ const step = this.properties.step || 1
+ const digits = step < 1 ? 1 : 0
+ const id = `range-${this.properties.label || this.name}`
+ for (
+ let i = this.properties.min;
+ i <= this.properties.max;
+ i += this.properties.step
+ ) {
+ options += ``
+ }
+ const datalist = L.DomUtil.element({
+ tagName: 'datalist',
+ parent: this.getHelpTextParent(),
+ className: 'umap-field-datalist',
+ safeHTML: options,
+ id: id,
+ })
+ this.input.setAttribute('list', id)
+ super.buildHelpText()
+ }
+}
+
+Fields.ManageOwner = class extends BaseElement {
+ build() {
+ const options = {
+ className: 'edit-owner',
+ on_select: L.bind(this.onSelect, this),
+ placeholder: translate("Type new owner's username"),
+ }
+ this.autocomplete = new AjaxAutocomplete(this.parentNode, options)
+ const owner = this.toHTML()
+ if (owner)
+ this.autocomplete.displaySelected({
+ item: { value: owner.id, label: owner.name },
+ })
+ }
+
+ value() {
+ return this._value
+ }
+
+ onSelect(choice) {
+ this._value = {
+ id: choice.item.value,
+ name: choice.item.label,
+ url: choice.item.url,
+ }
+ this.set()
+ }
+}
+
+Fields.ManageEditors = class extends BaseElement {
+ build() {
+ const options = {
+ className: 'edit-editors',
+ on_select: L.bind(this.onSelect, this),
+ on_unselect: L.bind(this.onUnselect, this),
+ placeholder: translate("Type editor's username"),
+ }
+ this.autocomplete = new AjaxAutocompleteMultiple(this.parentNode, options)
+ this._values = this.toHTML()
+ if (this._values)
+ for (let i = 0; i < this._values.length; i++)
+ this.autocomplete.displaySelected({
+ item: { value: this._values[i].id, label: this._values[i].name },
+ })
+ }
+
+ value() {
+ return this._values
+ }
+
+ onSelect(choice) {
+ this._values.push({
+ id: choice.item.value,
+ name: choice.item.label,
+ url: choice.item.url,
+ })
+ this.set()
+ }
+
+ onUnselect(choice) {
+ const index = this._values.findIndex((item) => item.id === choice.item.value)
+ if (index !== -1) {
+ this._values.splice(index, 1)
+ this.set()
+ }
+ }
+}
+
+Fields.ManageTeam = class extends Fields.IntSelect {
+ getOptions() {
+ return [[null, translate('None')]].concat(
+ this.properties.teams.map((team) => [team.id, team.name])
+ )
+ }
+
+ toHTML() {
+ return this.get()?.id
+ }
+
+ toJS() {
+ const value = this.value()
+ for (const team of this.properties.teams) {
+ if (team.id === value) return team
+ }
+ }
+}
diff --git a/umap/static/umap/js/modules/umap.js b/umap/static/umap/js/modules/umap.js
index 57ffc46b..543a5a94 100644
--- a/umap/static/umap/js/modules/umap.js
+++ b/umap/static/umap/js/modules/umap.js
@@ -34,6 +34,7 @@ import {
uMapAlert as Alert,
} from '../components/alerts/alert.js'
import Orderable from './orderable.js'
+import { DataForm } from './form/builder.js'
export default class Umap extends ServerStored {
constructor(element, geojson) {
@@ -734,7 +735,7 @@ export default class Umap extends ServerStored {
const metadataFields = ['properties.name', 'properties.description']
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption')
- const builder = new U.FormBuilder(this, metadataFields, {
+ const builder = new DataForm(this, metadataFields, {
className: 'map-metadata',
umap: this,
})
@@ -749,7 +750,7 @@ export default class Umap extends ServerStored {
'properties.permanentCredit',
'properties.permanentCreditBackground',
]
- const creditsBuilder = new U.FormBuilder(this, creditsFields, { umap: this })
+ const creditsBuilder = new DataForm(this, creditsFields, { umap: this })
credits.appendChild(creditsBuilder.build())
this.editPanel.open({ content: container })
}
@@ -770,7 +771,7 @@ export default class Umap extends ServerStored {
'properties.captionBar',
'properties.captionMenus',
])
- const builder = new U.FormBuilder(this, UIFields, { umap: this })
+ const builder = new DataForm(this, UIFields, { umap: this })
const controlsOptions = DomUtil.createFieldset(
container,
translate('User interface options')
@@ -793,7 +794,7 @@ export default class Umap extends ServerStored {
'properties.dashArray',
]
- const builder = new U.FormBuilder(this, shapeOptions, { umap: this })
+ const builder = new DataForm(this, shapeOptions, { umap: this })
const defaultShapeProperties = DomUtil.createFieldset(
container,
translate('Default shape properties')
@@ -812,7 +813,7 @@ export default class Umap extends ServerStored {
'properties.slugKey',
]
- const builder = new U.FormBuilder(this, optionsFields, { umap: this })
+ const builder = new DataForm(this, optionsFields, { umap: this })
const defaultProperties = DomUtil.createFieldset(
container,
translate('Default properties')
@@ -830,7 +831,7 @@ export default class Umap extends ServerStored {
'properties.labelInteractive',
'properties.outlinkTarget',
]
- const builder = new U.FormBuilder(this, popupFields, { umap: this })
+ const builder = new DataForm(this, popupFields, { umap: this })
const popupFieldset = DomUtil.createFieldset(
container,
translate('Default interaction options')
@@ -887,7 +888,7 @@ export default class Umap extends ServerStored {
container,
translate('Custom background')
)
- const builder = new U.FormBuilder(this, tilelayerFields, { umap: this })
+ const builder = new DataForm(this, tilelayerFields, { umap: this })
customTilelayer.appendChild(builder.build())
}
@@ -935,7 +936,7 @@ export default class Umap extends ServerStored {
['properties.overlay.tms', { handler: 'Switch', label: translate('TMS format') }],
]
const overlay = DomUtil.createFieldset(container, translate('Custom overlay'))
- const builder = new U.FormBuilder(this, overlayFields, { umap: this })
+ const builder = new DataForm(this, overlayFields, { umap: this })
overlay.appendChild(builder.build())
}
@@ -962,7 +963,7 @@ export default class Umap extends ServerStored {
{ handler: 'BlurFloatInput', placeholder: translate('max East') },
],
]
- const boundsBuilder = new U.FormBuilder(this, boundsFields, { umap: this })
+ const boundsBuilder = new DataForm(this, boundsFields, { umap: this })
limitBounds.appendChild(boundsBuilder.build())
const boundsButtons = DomUtil.create('div', 'button-bar half', limitBounds)
DomUtil.createButton(
@@ -1027,7 +1028,7 @@ export default class Umap extends ServerStored {
{ handler: 'Switch', label: translate('Autostart when map is loaded') },
],
]
- const slideshowBuilder = new U.FormBuilder(this, slideshowFields, {
+ const slideshowBuilder = new DataForm(this, slideshowFields, {
callback: () => {
this.slideshow.load()
// FIXME when we refactor formbuilder: this callback is called in a 'postsync'
@@ -1042,7 +1043,9 @@ export default class Umap extends ServerStored {
_editSync(container) {
const sync = DomUtil.createFieldset(container, translate('Real-time collaboration'))
- const builder = new U.FormBuilder(this, ['properties.syncEnabled'], { umap: this })
+ const builder = new DataForm(this, ['properties.syncEnabled'], {
+ umap: this,
+ })
sync.appendChild(builder.build())
}
@@ -1459,7 +1462,7 @@ export default class Umap extends ServerStored {
const row = DomUtil.create('li', 'orderable', ul)
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
datalayer.renderToolbox(row)
- const builder = new U.FormBuilder(
+ const builder = new DataForm(
datalayer,
[['options.name', { handler: 'EditableText' }]],
{ className: 'umap-form-inline' }
diff --git a/umap/static/umap/js/modules/utils.js b/umap/static/umap/js/modules/utils.js
index 2f70edf4..b5c4664f 100644
--- a/umap/static/umap/js/modules/utils.js
+++ b/umap/static/umap/js/modules/utils.js
@@ -446,3 +446,153 @@ export function eachElement(selector, callback) {
callback(el)
}
}
+
+export const COLORS = [
+ 'Black',
+ 'Navy',
+ 'DarkBlue',
+ 'MediumBlue',
+ 'Blue',
+ 'DarkGreen',
+ 'Green',
+ 'Teal',
+ 'DarkCyan',
+ 'DeepSkyBlue',
+ 'DarkTurquoise',
+ 'MediumSpringGreen',
+ 'Lime',
+ 'SpringGreen',
+ 'Aqua',
+ 'Cyan',
+ 'MidnightBlue',
+ 'DodgerBlue',
+ 'LightSeaGreen',
+ 'ForestGreen',
+ 'SeaGreen',
+ 'DarkSlateGray',
+ 'DarkSlateGrey',
+ 'LimeGreen',
+ 'MediumSeaGreen',
+ 'Turquoise',
+ 'RoyalBlue',
+ 'SteelBlue',
+ 'DarkSlateBlue',
+ 'MediumTurquoise',
+ 'Indigo',
+ 'DarkOliveGreen',
+ 'CadetBlue',
+ 'CornflowerBlue',
+ 'MediumAquaMarine',
+ 'DimGray',
+ 'DimGrey',
+ 'SlateBlue',
+ 'OliveDrab',
+ 'SlateGray',
+ 'SlateGrey',
+ 'LightSlateGray',
+ 'LightSlateGrey',
+ 'MediumSlateBlue',
+ 'LawnGreen',
+ 'Chartreuse',
+ 'Aquamarine',
+ 'Maroon',
+ 'Purple',
+ 'Olive',
+ 'Gray',
+ 'Grey',
+ 'SkyBlue',
+ 'LightSkyBlue',
+ 'BlueViolet',
+ 'DarkRed',
+ 'DarkMagenta',
+ 'SaddleBrown',
+ 'DarkSeaGreen',
+ 'LightGreen',
+ 'MediumPurple',
+ 'DarkViolet',
+ 'PaleGreen',
+ 'DarkOrchid',
+ 'YellowGreen',
+ 'Sienna',
+ 'Brown',
+ 'DarkGray',
+ 'DarkGrey',
+ 'LightBlue',
+ 'GreenYellow',
+ 'PaleTurquoise',
+ 'LightSteelBlue',
+ 'PowderBlue',
+ 'FireBrick',
+ 'DarkGoldenRod',
+ 'MediumOrchid',
+ 'RosyBrown',
+ 'DarkKhaki',
+ 'Silver',
+ 'MediumVioletRed',
+ 'IndianRed',
+ 'Peru',
+ 'Chocolate',
+ 'Tan',
+ 'LightGray',
+ 'LightGrey',
+ 'Thistle',
+ 'Orchid',
+ 'GoldenRod',
+ 'PaleVioletRed',
+ 'Crimson',
+ 'Gainsboro',
+ 'Plum',
+ 'BurlyWood',
+ 'LightCyan',
+ 'Lavender',
+ 'DarkSalmon',
+ 'Violet',
+ 'PaleGoldenRod',
+ 'LightCoral',
+ 'Khaki',
+ 'AliceBlue',
+ 'HoneyDew',
+ 'Azure',
+ 'SandyBrown',
+ 'Wheat',
+ 'Beige',
+ 'WhiteSmoke',
+ 'MintCream',
+ 'GhostWhite',
+ 'Salmon',
+ 'AntiqueWhite',
+ 'Linen',
+ 'LightGoldenRodYellow',
+ 'OldLace',
+ 'Red',
+ 'Fuchsia',
+ 'Magenta',
+ 'DeepPink',
+ 'OrangeRed',
+ 'Tomato',
+ 'HotPink',
+ 'Coral',
+ 'DarkOrange',
+ 'LightSalmon',
+ 'Orange',
+ 'LightPink',
+ 'Pink',
+ 'Gold',
+ 'PeachPuff',
+ 'NavajoWhite',
+ 'Moccasin',
+ 'Bisque',
+ 'MistyRose',
+ 'BlanchedAlmond',
+ 'PapayaWhip',
+ 'LavenderBlush',
+ 'SeaShell',
+ 'Cornsilk',
+ 'LemonChiffon',
+ 'FloralWhite',
+ 'Snow',
+ 'Yellow',
+ 'LightYellow',
+ 'Ivory',
+ 'White',
+]
diff --git a/umap/static/umap/js/umap.forms.js b/umap/static/umap/js/umap.forms.js
deleted file mode 100644
index dc90d168..00000000
--- a/umap/static/umap/js/umap.forms.js
+++ /dev/null
@@ -1,1242 +0,0 @@
-U.COLORS = [
- 'Black',
- 'Navy',
- 'DarkBlue',
- 'MediumBlue',
- 'Blue',
- 'DarkGreen',
- 'Green',
- 'Teal',
- 'DarkCyan',
- 'DeepSkyBlue',
- 'DarkTurquoise',
- 'MediumSpringGreen',
- 'Lime',
- 'SpringGreen',
- 'Aqua',
- 'Cyan',
- 'MidnightBlue',
- 'DodgerBlue',
- 'LightSeaGreen',
- 'ForestGreen',
- 'SeaGreen',
- 'DarkSlateGray',
- 'DarkSlateGrey',
- 'LimeGreen',
- 'MediumSeaGreen',
- 'Turquoise',
- 'RoyalBlue',
- 'SteelBlue',
- 'DarkSlateBlue',
- 'MediumTurquoise',
- 'Indigo',
- 'DarkOliveGreen',
- 'CadetBlue',
- 'CornflowerBlue',
- 'MediumAquaMarine',
- 'DimGray',
- 'DimGrey',
- 'SlateBlue',
- 'OliveDrab',
- 'SlateGray',
- 'SlateGrey',
- 'LightSlateGray',
- 'LightSlateGrey',
- 'MediumSlateBlue',
- 'LawnGreen',
- 'Chartreuse',
- 'Aquamarine',
- 'Maroon',
- 'Purple',
- 'Olive',
- 'Gray',
- 'Grey',
- 'SkyBlue',
- 'LightSkyBlue',
- 'BlueViolet',
- 'DarkRed',
- 'DarkMagenta',
- 'SaddleBrown',
- 'DarkSeaGreen',
- 'LightGreen',
- 'MediumPurple',
- 'DarkViolet',
- 'PaleGreen',
- 'DarkOrchid',
- 'YellowGreen',
- 'Sienna',
- 'Brown',
- 'DarkGray',
- 'DarkGrey',
- 'LightBlue',
- 'GreenYellow',
- 'PaleTurquoise',
- 'LightSteelBlue',
- 'PowderBlue',
- 'FireBrick',
- 'DarkGoldenRod',
- 'MediumOrchid',
- 'RosyBrown',
- 'DarkKhaki',
- 'Silver',
- 'MediumVioletRed',
- 'IndianRed',
- 'Peru',
- 'Chocolate',
- 'Tan',
- 'LightGray',
- 'LightGrey',
- 'Thistle',
- 'Orchid',
- 'GoldenRod',
- 'PaleVioletRed',
- 'Crimson',
- 'Gainsboro',
- 'Plum',
- 'BurlyWood',
- 'LightCyan',
- 'Lavender',
- 'DarkSalmon',
- 'Violet',
- 'PaleGoldenRod',
- 'LightCoral',
- 'Khaki',
- 'AliceBlue',
- 'HoneyDew',
- 'Azure',
- 'SandyBrown',
- 'Wheat',
- 'Beige',
- 'WhiteSmoke',
- 'MintCream',
- 'GhostWhite',
- 'Salmon',
- 'AntiqueWhite',
- 'Linen',
- 'LightGoldenRodYellow',
- 'OldLace',
- 'Red',
- 'Fuchsia',
- 'Magenta',
- 'DeepPink',
- 'OrangeRed',
- 'Tomato',
- 'HotPink',
- 'Coral',
- 'DarkOrange',
- 'LightSalmon',
- 'Orange',
- 'LightPink',
- 'Pink',
- 'Gold',
- 'PeachPuff',
- 'NavajoWhite',
- 'Moccasin',
- 'Bisque',
- 'MistyRose',
- 'BlanchedAlmond',
- 'PapayaWhip',
- 'LavenderBlush',
- 'SeaShell',
- 'Cornsilk',
- 'LemonChiffon',
- 'FloralWhite',
- 'Snow',
- 'Yellow',
- 'LightYellow',
- 'Ivory',
- 'White',
-]
-
-L.FormBuilder.Element.include({
- undefine: function () {
- L.DomUtil.addClass(this.wrapper, 'undefined')
- this.clear()
- this.sync()
- },
-
- getParentNode: function () {
- if (this.options.wrapper) {
- return L.DomUtil.create(
- this.options.wrapper,
- this.options.wrapperClass || '',
- this.form
- )
- }
- let className = 'formbox'
- if (this.options.inheritable) {
- className +=
- this.get(true) === undefined ? ' inheritable undefined' : ' inheritable '
- }
- className += ` umap-field-${this.name}`
- this.wrapper = L.DomUtil.create('div', className, this.form)
- this.header = L.DomUtil.create('div', 'header', this.wrapper)
- if (this.options.inheritable) {
- const undefine = L.DomUtil.add('a', 'button undefine', this.header, L._('clear'))
- const define = L.DomUtil.add('a', 'button define', this.header, L._('define'))
- L.DomEvent.on(
- define,
- 'click',
- function (e) {
- L.DomEvent.stop(e)
- this.fetch()
- this.fire('define')
- L.DomUtil.removeClass(this.wrapper, 'undefined')
- },
- this
- )
- L.DomEvent.on(undefine, 'click', L.DomEvent.stop).on(
- undefine,
- 'click',
- this.undefine,
- this
- )
- }
- this.quickContainer = L.DomUtil.create(
- 'span',
- 'quick-actions show-on-defined',
- this.header
- )
- this.extendedContainer = L.DomUtil.create('div', 'show-on-defined', this.wrapper)
- return this.extendedContainer
- },
-
- getLabelParent: function () {
- return this.header
- },
-
- clear: function () {
- this.input.value = ''
- },
-
- get: function (own) {
- if (!this.options.inheritable || own) return this.builder.getter(this.field)
- const path = this.field.split('.')
- const key = path[path.length - 1]
- return this.obj.getOption(key)
- },
-
- buildLabel: function () {
- if (this.options.label) {
- this.label = L.DomUtil.create('label', '', this.getLabelParent())
- this.label.textContent = this.label.title = this.options.label
- if (this.options.helpEntries) {
- this.builder._umap.help.button(this.label, this.options.helpEntries)
- } else if (this.options.helpTooltip) {
- const info = L.DomUtil.create('i', 'info', this.label)
- L.DomEvent.on(info, 'mouseover', () => {
- this.builder._umap.tooltip.open({
- anchor: info,
- content: this.options.helpTooltip,
- position: 'top',
- })
- })
- }
- }
- },
-})
-
-L.FormBuilder.Select.include({
- clear: function () {
- this.select.value = ''
- },
-
- getDefault: function () {
- if (this.options.inheritable) return undefined
- return this.getOptions()[0][0]
- },
-})
-
-L.FormBuilder.CheckBox.include({
- value: function () {
- return L.DomUtil.hasClass(this.wrapper, 'undefined')
- ? undefined
- : this.input.checked
- },
-
- clear: function () {
- this.fetch()
- },
-})
-
-L.FormBuilder.EditableText = L.FormBuilder.Element.extend({
- build: function () {
- this.input = L.DomUtil.create('span', this.options.className || '', this.parentNode)
- this.input.contentEditable = true
- this.fetch()
- L.DomEvent.on(this.input, 'input', this.sync, this)
- L.DomEvent.on(this.input, 'keypress', this.onKeyPress, this)
- },
-
- getParentNode: function () {
- return this.form
- },
-
- value: function () {
- return this.input.textContent
- },
-
- fetch: function () {
- this.input.textContent = this.toHTML()
- },
-
- onKeyPress: function (event) {
- if (event.keyCode === 13) {
- event.preventDefault()
- this.input.blur()
- }
- },
-})
-
-L.FormBuilder.ColorPicker = L.FormBuilder.Input.extend({
- colors: U.COLORS,
- getParentNode: function () {
- L.FormBuilder.CheckBox.prototype.getParentNode.call(this)
- return this.quickContainer
- },
-
- build: function () {
- L.FormBuilder.Input.prototype.build.call(this)
- this.input.placeholder = this.options.placeholder || L._('Inherit')
- this.container = L.DomUtil.create(
- 'div',
- 'umap-color-picker',
- this.extendedContainer
- )
- this.container.style.display = 'none'
- for (const idx in this.colors) {
- this.addColor(this.colors[idx])
- }
- this.spreadColor()
- this.input.autocomplete = 'off'
- L.DomEvent.on(this.input, 'focus', this.onFocus, this)
- L.DomEvent.on(this.input, 'blur', this.onBlur, this)
- L.DomEvent.on(this.input, 'change', this.sync, this)
- this.on('define', this.onFocus)
- },
-
- onFocus: function () {
- this.container.style.display = 'block'
- this.spreadColor()
- },
-
- onBlur: function () {
- const closePicker = () => {
- this.container.style.display = 'none'
- }
- // We must leave time for the click to be listened.
- window.setTimeout(closePicker, 100)
- },
-
- sync: function () {
- this.spreadColor()
- L.FormBuilder.Input.prototype.sync.call(this)
- },
-
- spreadColor: function () {
- if (this.input.value) this.input.style.backgroundColor = this.input.value
- else this.input.style.backgroundColor = 'inherit'
- },
-
- addColor: function (colorName) {
- const span = L.DomUtil.create('span', '', this.container)
- span.style.backgroundColor = span.title = colorName
- const updateColorInput = function () {
- this.input.value = colorName
- this.sync()
- this.container.style.display = 'none'
- }
- L.DomEvent.on(span, 'mousedown', updateColorInput, this)
- },
-})
-
-L.FormBuilder.TextColorPicker = L.FormBuilder.ColorPicker.extend({
- colors: [
- 'Black',
- 'DarkSlateGrey',
- 'DimGrey',
- 'SlateGrey',
- 'LightSlateGrey',
- 'Grey',
- 'DarkGrey',
- 'LightGrey',
- 'White',
- ],
-})
-
-L.FormBuilder.LayerTypeChooser = L.FormBuilder.Select.extend({
- getOptions: () => {
- return U.LAYER_TYPES.map((class_) => [class_.TYPE, class_.NAME])
- },
-})
-
-L.FormBuilder.SlideshowDelay = L.FormBuilder.IntSelect.extend({
- getOptions: () => {
- const options = []
- for (let i = 1; i < 30; i++) {
- options.push([i * 1000, L._('{delay} seconds', { delay: i })])
- }
- return options
- },
-})
-
-L.FormBuilder.DataLayerSwitcher = L.FormBuilder.Select.extend({
- getOptions: function () {
- const options = []
- this.builder._umap.eachDataLayerReverse((datalayer) => {
- if (
- datalayer.isLoaded() &&
- !datalayer.isDataReadOnly() &&
- datalayer.isBrowsable()
- ) {
- options.push([L.stamp(datalayer), datalayer.getName()])
- }
- })
- return options
- },
-
- toHTML: function () {
- return L.stamp(this.obj.datalayer)
- },
-
- toJS: function () {
- return this.builder._umap.datalayers[this.value()]
- },
-
- set: function () {
- this.builder._umap.lastUsedDataLayer = this.toJS()
- this.obj.changeDataLayer(this.toJS())
- },
-})
-
-L.FormBuilder.DataFormat = L.FormBuilder.Select.extend({
- selectOptions: [
- [undefined, L._('Choose the data format')],
- ['geojson', 'geojson'],
- ['osm', 'osm'],
- ['csv', 'csv'],
- ['gpx', 'gpx'],
- ['kml', 'kml'],
- ['georss', 'georss'],
- ],
-})
-
-L.FormBuilder.LicenceChooser = L.FormBuilder.Select.extend({
- getOptions: function () {
- const licences = []
- const licencesList = this.builder.obj.properties.licences
- let licence
- for (const i in licencesList) {
- licence = licencesList[i]
- licences.push([i, licence.name])
- }
- return licences
- },
-
- toHTML: function () {
- return this.get()?.name
- },
-
- toJS: function () {
- return this.builder.obj.properties.licences[this.value()]
- },
-})
-
-L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({
- selectOptions: [
- [undefined, L._('inherit')],
- [true, L._('yes')],
- [false, L._('no')],
- ],
-
- toJS: function () {
- let value = this.value()
- switch (value) {
- case 'true':
- case true:
- value = true
- break
- case 'false':
- case false:
- value = false
- break
- default:
- value = undefined
- }
- return value
- },
-})
-
-L.FormBuilder.BlurInput.include({
- build: function () {
- this.options.className = 'blur'
- L.FormBuilder.Input.prototype.build.call(this)
- const button = L.DomUtil.create('span', 'button blur-button')
- L.DomUtil.after(this.input, button)
- L.DomEvent.on(this.input, 'focus', this.fetch, this)
- },
-})
-
-// Adds an autocomplete using all available user defined properties
-L.FormBuilder.PropertyInput = L.FormBuilder.BlurInput.extend({
- build: function () {
- L.FormBuilder.BlurInput.prototype.build.call(this)
- const autocomplete = new U.AutocompleteDatalist(this.input)
- // Will be used on Umap and DataLayer
- const properties = this.builder.obj.allProperties()
- autocomplete.suggestions = properties
- },
-})
-
-L.FormBuilder.IconUrl = L.FormBuilder.BlurInput.extend({
- type: () => 'hidden',
-
- build: function () {
- L.FormBuilder.BlurInput.prototype.build.call(this)
- this.buttons = L.DomUtil.create('div', '', this.parentNode)
- this.tabs = L.DomUtil.create('div', 'flat-tabs', this.parentNode)
- this.body = L.DomUtil.create('div', 'umap-pictogram-body', this.parentNode)
- this.footer = L.DomUtil.create('div', '', this.parentNode)
- this.updatePreview()
- this.on('define', this.onDefine)
- },
-
- onDefine: async function () {
- this.buttons.innerHTML = ''
- this.footer.innerHTML = ''
- const [{ pictogram_list }, response, error] = await this.builder._umap.server.get(
- this.builder._umap.properties.urls.pictogram_list_json
- )
- if (!error) this.pictogram_list = pictogram_list
- this.buildTabs()
- const value = this.value()
- if (U.Icon.RECENT.length) this.showRecentTab()
- else if (!value || U.Utils.isPath(value)) this.showSymbolsTab()
- else if (U.Utils.isRemoteUrl(value) || U.Utils.isDataImage(value)) this.showURLTab()
- else this.showCharsTab()
- const closeButton = L.DomUtil.createButton(
- 'button action-button',
- this.footer,
- L._('Close'),
- function (e) {
- this.body.innerHTML = ''
- this.tabs.innerHTML = ''
- this.footer.innerHTML = ''
- if (this.isDefault()) this.undefine(e)
- else this.updatePreview()
- },
- this
- )
- },
-
- buildTabs: function () {
- this.tabs.innerHTML = ''
- if (U.Icon.RECENT.length) {
- const recent = L.DomUtil.add(
- 'button',
- 'flat tab-recent',
- this.tabs,
- L._('Recent')
- )
- L.DomEvent.on(recent, 'click', L.DomEvent.stop).on(
- recent,
- 'click',
- this.showRecentTab,
- this
- )
- }
- const symbol = L.DomUtil.add('button', 'flat tab-symbols', this.tabs, L._('Symbol'))
- const char = L.DomUtil.add(
- 'button',
- 'flat tab-chars',
- this.tabs,
- L._('Emoji & Character')
- )
- url = L.DomUtil.add('button', 'flat tab-url', this.tabs, L._('URL'))
- L.DomEvent.on(symbol, 'click', L.DomEvent.stop).on(
- symbol,
- 'click',
- this.showSymbolsTab,
- this
- )
- L.DomEvent.on(char, 'click', L.DomEvent.stop).on(
- char,
- 'click',
- this.showCharsTab,
- this
- )
- L.DomEvent.on(url, 'click', L.DomEvent.stop).on(url, 'click', this.showURLTab, this)
- },
-
- openTab: function (name) {
- const els = this.tabs.querySelectorAll('button')
- for (const el of els) {
- L.DomUtil.removeClass(el, 'on')
- }
- const el = this.tabs.querySelector(`.tab-${name}`)
- L.DomUtil.addClass(el, 'on')
- this.body.innerHTML = ''
- },
-
- updatePreview: function () {
- this.buttons.innerHTML = ''
- if (this.isDefault()) return
- if (!U.Utils.hasVar(this.value())) {
- // Do not try to render URL with variables
- const box = L.DomUtil.create('div', 'umap-pictogram-choice', this.buttons)
- L.DomEvent.on(box, 'click', this.onDefine, this)
- const icon = U.Icon.makeElement(this.value(), box)
- }
- this.button = L.DomUtil.createButton(
- 'button action-button',
- this.buttons,
- this.value() ? L._('Change') : L._('Add'),
- this.onDefine,
- this
- )
- },
-
- addIconPreview: function (pictogram, parent) {
- const baseClass = 'umap-pictogram-choice'
- const value = pictogram.src
- const search = U.Utils.normalize(this.searchInput.value)
- const title = pictogram.attribution
- ? `${pictogram.name} — © ${pictogram.attribution}`
- : pictogram.name || pictogram.src
- if (search && U.Utils.normalize(title).indexOf(search) === -1) return
- const className = value === this.value() ? `${baseClass} selected` : baseClass
- const container = L.DomUtil.create('div', className, parent)
- U.Icon.makeElement(value, container)
- container.title = title
- L.DomEvent.on(
- container,
- 'click',
- function (e) {
- this.input.value = value
- this.sync()
- this.unselectAll(this.grid)
- L.DomUtil.addClass(container, 'selected')
- },
- this
- )
- return true // Icon has been added (not filtered)
- },
-
- clear: function () {
- this.input.value = ''
- this.unselectAll(this.body)
- this.sync()
- this.body.innerHTML = ''
- this.updatePreview()
- },
-
- addCategory: function (items, name) {
- const parent = L.DomUtil.create('div', 'umap-pictogram-category')
- if (name) L.DomUtil.add('h6', '', parent, name)
- const grid = L.DomUtil.create('div', 'umap-pictogram-grid', parent)
- let status = false
- for (const item of items) {
- status = this.addIconPreview(item, grid) || status
- }
- if (status) this.grid.appendChild(parent)
- },
-
- buildSymbolsList: function () {
- this.grid.innerHTML = ''
- const categories = {}
- let category
- for (const props of this.pictogram_list) {
- category = props.category || L._('Generic')
- categories[category] = categories[category] || []
- categories[category].push(props)
- }
- const sorted = Object.entries(categories).toSorted(([a], [b]) =>
- U.Utils.naturalSort(a, b, U.lang)
- )
- for (const [name, items] of sorted) {
- this.addCategory(items, name)
- }
- },
-
- buildRecentList: function () {
- this.grid.innerHTML = ''
- const items = U.Icon.RECENT.map((src) => ({
- src,
- }))
- this.addCategory(items)
- },
-
- isDefault: function () {
- return !this.value() || this.value() === U.SCHEMA.iconUrl.default
- },
-
- addGrid: function (onSearch) {
- this.searchInput = L.DomUtil.create('input', '', this.body)
- this.searchInput.type = 'search'
- this.searchInput.placeholder = L._('Search')
- this.grid = L.DomUtil.create('div', '', this.body)
- L.DomEvent.on(this.searchInput, 'input', onSearch, this)
- },
-
- showRecentTab: function () {
- if (!U.Icon.RECENT.length) return
- this.openTab('recent')
- this.addGrid(this.buildRecentList)
- this.buildRecentList()
- },
-
- showSymbolsTab: function () {
- this.openTab('symbols')
- this.addGrid(this.buildSymbolsList)
- this.buildSymbolsList()
- },
-
- showCharsTab: function () {
- this.openTab('chars')
- const value = !U.Icon.isImg(this.value()) ? this.value() : null
- const input = this.buildInput(this.body, value)
- input.placeholder = L._('Type char or paste emoji')
- input.type = 'text'
- },
-
- showURLTab: function () {
- this.openTab('url')
- const value =
- U.Utils.isRemoteUrl(this.value()) || U.Utils.isDataImage(this.value())
- ? this.value()
- : null
- const input = this.buildInput(this.body, value)
- input.placeholder = L._('Add image URL')
- input.type = 'url'
- },
-
- buildInput: function (parent, value) {
- const input = L.DomUtil.create('input', 'blur', parent)
- const button = L.DomUtil.create('span', 'button blur-button', parent)
- if (value) input.value = value
- L.DomEvent.on(input, 'blur', () => {
- // Do not clear this.input when focus-blur
- // empty input
- if (input.value === value) return
- this.input.value = input.value
- this.sync()
- })
- return input
- },
-
- unselectAll: (container) => {
- const els = container.querySelectorAll('div.selected')
- for (const el in els) {
- if (els.hasOwnProperty(el)) L.DomUtil.removeClass(els[el], 'selected')
- }
- },
-})
-
-L.FormBuilder.Url = L.FormBuilder.Input.extend({
- type: () => 'url',
-})
-
-L.FormBuilder.Switch = L.FormBuilder.CheckBox.extend({
- getParentNode: function () {
- L.FormBuilder.CheckBox.prototype.getParentNode.call(this)
- if (this.options.inheritable) return this.quickContainer
- return this.extendedContainer
- },
-
- build: function () {
- L.FormBuilder.CheckBox.prototype.build.apply(this)
- if (this.options.inheritable)
- this.label = L.DomUtil.create('label', '', this.input.parentNode)
- else this.input.parentNode.appendChild(this.label)
- L.DomUtil.addClass(this.input.parentNode, 'with-switch')
- const id = `${this.builder.options.id || Date.now()}.${this.name}`
- this.label.setAttribute('for', id)
- L.DomUtil.addClass(this.input, 'switch')
- this.input.id = id
- },
-})
-
-L.FormBuilder.FacetSearchBase = L.FormBuilder.Element.extend({
- buildLabel: function () {
- this.label = L.DomUtil.element({
- tagName: 'legend',
- textContent: this.options.label,
- })
- },
-})
-L.FormBuilder.FacetSearchChoices = L.FormBuilder.FacetSearchBase.extend({
- build: function () {
- this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
- this.container.appendChild(this.label)
- this.ul = L.DomUtil.create('ul', '', this.container)
- this.type = this.options.criteria.type
-
- const choices = this.options.criteria.choices
- choices.sort()
- choices.forEach((value) => this.buildLi(value))
- },
-
- buildLi: function (value) {
- const property_li = L.DomUtil.create('li', '', this.ul)
- const label = L.DomUtil.create('label', '', property_li)
- const input = L.DomUtil.create('input', '', label)
- L.DomUtil.add('span', '', label, value)
-
- input.type = this.type
- input.name = `${this.type}_${this.name}`
- input.checked = this.get().choices.includes(value)
- input.dataset.value = value
-
- L.DomEvent.on(input, 'change', (e) => this.sync())
- },
-
- toJS: function () {
- return {
- type: this.type,
- choices: [...this.ul.querySelectorAll('input:checked')].map(
- (i) => i.dataset.value
- ),
- }
- },
-})
-
-L.FormBuilder.MinMaxBase = L.FormBuilder.FacetSearchBase.extend({
- getInputType: (type) => type,
-
- getLabels: () => [L._('Min'), L._('Max')],
-
- prepareForHTML: (value) => value.valueOf(),
-
- build: function () {
- this.container = L.DomUtil.create('fieldset', 'umap-facet', this.parentNode)
- this.container.appendChild(this.label)
- const { min, max, type } = this.options.criteria
- const { min: modifiedMin, max: modifiedMax } = this.get()
-
- const currentMin = modifiedMin !== undefined ? modifiedMin : min
- const currentMax = modifiedMax !== undefined ? modifiedMax : max
- this.type = type
- this.inputType = this.getInputType(this.type)
-
- const [minLabel, maxLabel] = this.getLabels()
-
- this.minLabel = L.DomUtil.create('label', '', this.container)
- this.minLabel.textContent = minLabel
-
- this.minInput = L.DomUtil.create('input', '', this.minLabel)
- this.minInput.type = this.inputType
- this.minInput.step = 'any'
- this.minInput.min = this.prepareForHTML(min)
- this.minInput.max = this.prepareForHTML(max)
- if (min != null) {
- // The value stored using setAttribute is not modified by
- // user input, and will be used as initial value when calling
- // form.reset(), and can also be retrieve later on by using
- // getAttributing, to compare with current value and know
- // if this value has been modified by the user
- // https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/reset
- this.minInput.setAttribute('value', this.prepareForHTML(min))
- this.minInput.value = this.prepareForHTML(currentMin)
- }
-
- this.maxLabel = L.DomUtil.create('label', '', this.container)
- this.maxLabel.textContent = maxLabel
-
- this.maxInput = L.DomUtil.create('input', '', this.maxLabel)
- this.maxInput.type = this.inputType
- this.maxInput.step = 'any'
- this.maxInput.min = this.prepareForHTML(min)
- this.maxInput.max = this.prepareForHTML(max)
- if (max != null) {
- // Cf comment above about setAttribute vs value
- this.maxInput.setAttribute('value', this.prepareForHTML(max))
- this.maxInput.value = this.prepareForHTML(currentMax)
- }
- this.toggleStatus()
-
- L.DomEvent.on(this.minInput, 'change', () => this.sync())
- L.DomEvent.on(this.maxInput, 'change', () => this.sync())
- },
-
- toggleStatus: function () {
- this.minInput.dataset.modified = this.isMinModified()
- this.maxInput.dataset.modified = this.isMaxModified()
- },
-
- sync: function () {
- L.FormBuilder.Element.prototype.sync.call(this)
- this.toggleStatus()
- },
-
- isMinModified: function () {
- const default_ = this.minInput.getAttribute('value')
- const current = this.minInput.value
- return current !== default_
- },
-
- isMaxModified: function () {
- const default_ = this.maxInput.getAttribute('value')
- const current = this.maxInput.value
- return current !== default_
- },
-
- toJS: function () {
- const opts = {
- type: this.type,
- }
- if (this.minInput.value !== '' && this.isMinModified()) {
- opts.min = this.prepareForJS(this.minInput.value)
- }
- if (this.maxInput.value !== '' && this.isMaxModified()) {
- opts.max = this.prepareForJS(this.maxInput.value)
- }
- return opts
- },
-})
-
-L.FormBuilder.FacetSearchNumber = L.FormBuilder.MinMaxBase.extend({
- prepareForJS: (value) => new Number(value),
-})
-
-L.FormBuilder.FacetSearchDate = L.FormBuilder.MinMaxBase.extend({
- prepareForJS: (value) => new Date(value),
-
- toLocaleDateTime: (dt) => new Date(dt.valueOf() - dt.getTimezoneOffset() * 60000),
-
- prepareForHTML: function (value) {
- // Value must be in local time
- if (Number.isNaN(value)) return
- return this.toLocaleDateTime(value).toISOString().substr(0, 10)
- },
-
- getLabels: () => [L._('From'), L._('Until')],
-})
-
-L.FormBuilder.FacetSearchDateTime = L.FormBuilder.FacetSearchDate.extend({
- getInputType: (type) => 'datetime-local',
-
- prepareForHTML: function (value) {
- // Value must be in local time
- if (Number.isNaN(value)) return
- return this.toLocaleDateTime(value).toISOString().slice(0, -1)
- },
-})
-
-L.FormBuilder.MultiChoice = L.FormBuilder.Element.extend({
- default: 'null',
- className: 'umap-multiplechoice',
-
- clear: function () {
- const checked = this.container.querySelector('input[type="radio"]:checked')
- if (checked) checked.checked = false
- },
-
- fetch: function () {
- this.initial = this.toHTML()
- let value = this.initial
- if (!this.container.querySelector(`input[type="radio"][value="${value}"]`)) {
- value = this.options.default !== undefined ? this.options.default : this.default
- }
- const choices = this.getChoices().map(([value, label]) => `${value}`)
- if (choices.includes(`${value}`)) {
- this.container.querySelector(`input[type="radio"][value="${value}"]`).checked =
- true
- }
- },
-
- value: function () {
- const checked = this.container.querySelector('input[type="radio"]:checked')
- if (checked) return checked.value
- },
-
- getChoices: function () {
- return this.options.choices || this.choices
- },
-
- build: function () {
- const choices = this.getChoices()
- this.container = L.DomUtil.create(
- 'div',
- `${this.className} by${choices.length}`,
- this.parentNode
- )
- for (const [i, [value, label]] of choices.entries()) {
- this.addChoice(value, label, i)
- }
- this.fetch()
- },
-
- addChoice: function (value, label, counter) {
- const input = L.DomUtil.create('input', '', this.container)
- label = L.DomUtil.add('label', '', this.container, label)
- input.type = 'radio'
- input.name = this.name
- input.value = value
- const id = `${Date.now()}.${this.name}.${counter}`
- label.setAttribute('for', id)
- input.id = id
- L.DomEvent.on(input, 'change', this.sync, this)
- },
-})
-
-L.FormBuilder.TernaryChoices = L.FormBuilder.MultiChoice.extend({
- default: 'null',
-
- toJS: function () {
- let value = this.value()
- switch (value) {
- case 'true':
- case true:
- value = true
- break
- case 'false':
- case false:
- value = false
- break
- case 'null':
- case null:
- value = null
- break
- default:
- value = undefined
- }
- return value
- },
-})
-
-L.FormBuilder.NullableChoices = L.FormBuilder.TernaryChoices.extend({
- choices: [
- [true, L._('always')],
- [false, L._('never')],
- ['null', L._('hidden')],
- ],
-})
-
-L.FormBuilder.DataLayersControl = L.FormBuilder.TernaryChoices.extend({
- choices: [
- [true, L._('collapsed')],
- ['expanded', L._('expanded')],
- [false, L._('never')],
- ['null', L._('hidden')],
- ],
-
- toJS: function () {
- let value = this.value()
- if (value !== 'expanded')
- value = L.FormBuilder.TernaryChoices.prototype.toJS.call(this)
- return value
- },
-})
-
-L.FormBuilder.Range = L.FormBuilder.FloatInput.extend({
- type: () => 'range',
-
- value: function () {
- return L.DomUtil.hasClass(this.wrapper, 'undefined')
- ? undefined
- : L.FormBuilder.FloatInput.prototype.value.call(this)
- },
-
- buildHelpText: function () {
- let options = ''
- const step = this.options.step || 1
- const digits = step < 1 ? 1 : 0
- const id = `range-${this.options.label || this.name}`
- for (let i = this.options.min; i <= this.options.max; i += this.options.step) {
- options += ``
- }
- const datalist = L.DomUtil.element({
- tagName: 'datalist',
- parent: this.getHelpTextParent(),
- className: 'umap-field-datalist',
- safeHTML: options,
- id: id,
- })
- this.input.setAttribute('list', id)
- L.FormBuilder.Input.prototype.buildHelpText.call(this)
- },
-})
-
-L.FormBuilder.ManageOwner = L.FormBuilder.Element.extend({
- build: function () {
- const options = {
- className: 'edit-owner',
- on_select: L.bind(this.onSelect, this),
- placeholder: L._("Type new owner's username"),
- }
- this.autocomplete = new U.AjaxAutocomplete(this.parentNode, options)
- const owner = this.toHTML()
- if (owner)
- this.autocomplete.displaySelected({
- item: { value: owner.id, label: owner.name },
- })
- },
-
- value: function () {
- return this._value
- },
-
- onSelect: function (choice) {
- this._value = {
- id: choice.item.value,
- name: choice.item.label,
- url: choice.item.url,
- }
- this.set()
- },
-})
-
-L.FormBuilder.ManageEditors = L.FormBuilder.Element.extend({
- build: function () {
- const options = {
- className: 'edit-editors',
- on_select: L.bind(this.onSelect, this),
- on_unselect: L.bind(this.onUnselect, this),
- placeholder: L._("Type editor's username"),
- }
- this.autocomplete = new U.AjaxAutocompleteMultiple(this.parentNode, options)
- this._values = this.toHTML()
- if (this._values)
- for (let i = 0; i < this._values.length; i++)
- this.autocomplete.displaySelected({
- item: { value: this._values[i].id, label: this._values[i].name },
- })
- },
-
- value: function () {
- return this._values
- },
-
- onSelect: function (choice) {
- this._values.push({
- id: choice.item.value,
- name: choice.item.label,
- url: choice.item.url,
- })
- this.set()
- },
-
- onUnselect: function (choice) {
- const index = this._values.findIndex((item) => item.id === choice.item.value)
- if (index !== -1) {
- this._values.splice(index, 1)
- this.set()
- }
- },
-})
-
-L.FormBuilder.ManageTeam = L.FormBuilder.IntSelect.extend({
- getOptions: function () {
- return [[null, L._('None')]].concat(
- this.options.teams.map((team) => [team.id, team.name])
- )
- },
- toHTML: function () {
- return this.get()?.id
- },
- toJS: function () {
- const value = this.value()
- for (const team of this.options.teams) {
- if (team.id === value) return team
- }
- },
-})
-
-U.FormBuilder = L.FormBuilder.extend({
- options: {
- className: 'umap-form',
- },
-
- customHandlers: {
- sortKey: 'PropertyInput',
- easing: 'Switch',
- facetKey: 'PropertyInput',
- slugKey: 'PropertyInput',
- labelKey: 'PropertyInput',
- },
-
- computeDefaultOptions: function () {
- for (const [key, schema] of Object.entries(U.SCHEMA)) {
- if (schema.type === Boolean) {
- if (schema.nullable) schema.handler = 'NullableChoices'
- else schema.handler = 'Switch'
- } else if (schema.type === 'Text') {
- schema.handler = 'Textarea'
- } else if (schema.type === Number) {
- if (schema.step) schema.handler = 'Range'
- else schema.handler = 'IntInput'
- } else if (schema.choices) {
- const text_length = schema.choices.reduce(
- (acc, [_, label]) => acc + label.length,
- 0
- )
- // Try to be smart and use MultiChoice only
- // for choices where labels are shorts…
- if (text_length < 40) {
- schema.handler = 'MultiChoice'
- } else {
- schema.handler = 'Select'
- schema.selectOptions = schema.choices
- }
- } else {
- switch (key) {
- case 'color':
- case 'fillColor':
- schema.handler = 'ColorPicker'
- break
- case 'iconUrl':
- schema.handler = 'IconUrl'
- break
- case 'licence':
- schema.handler = 'LicenceChooser'
- break
- }
- }
- if (this.customHandlers[key]) {
- schema.handler = this.customHandlers[key]
- }
- // FormBuilder use this key for the input type itself
- delete schema.type
- this.defaultOptions[key] = schema
- }
- },
-
- initialize: function (obj, fields, options = {}) {
- this._umap = obj._umap || options.umap
- this.computeDefaultOptions()
- L.FormBuilder.prototype.initialize.call(this, obj, fields, options)
- this.on('finish', this.finish)
- },
-
- setter: function (field, value) {
- L.FormBuilder.prototype.setter.call(this, field, value)
- this.obj.isDirty = true
- if ('render' in this.obj) {
- this.obj.render([field], this)
- }
- if ('sync' in this.obj) {
- this.obj.sync.update(field, value)
- }
- },
-
- getter: function (field) {
- const path = field.split('.')
- let value = this.obj
- let sub
- for (sub of path) {
- try {
- value = value[sub]
- } catch {
- console.log(field)
- }
- }
- if (value === undefined) values = U.SCHEMA[sub]?.default
- return value
- },
-
- finish: (event) => {
- event.helper?.input?.blur()
- },
-})
diff --git a/umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js b/umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js
deleted file mode 100644
index 6f814904..00000000
--- a/umap/static/umap/vendors/formbuilder/Leaflet.FormBuilder.js
+++ /dev/null
@@ -1,468 +0,0 @@
-L.FormBuilder = L.Evented.extend({
- options: {
- className: 'leaflet-form',
- },
-
- defaultOptions: {
- // Eg.:
- // name: {label: L._('name')},
- // description: {label: L._('description'), handler: 'Textarea'},
- // opacity: {label: L._('opacity'), helpText: L._('Opacity, from 0.1 to 1.0 (opaque).')},
- },
-
- initialize: function (obj, fields, options) {
- L.setOptions(this, options)
- this.obj = obj
- this.form = L.DomUtil.create('form', this.options.className)
- this.setFields(fields)
- if (this.options.id) {
- this.form.id = this.options.id
- }
- if (this.options.className) {
- L.DomUtil.addClass(this.form, this.options.className)
- }
- },
-
- setFields: function (fields) {
- this.fields = fields || []
- this.helpers = {}
- },
-
- build: function () {
- this.form.innerHTML = ''
- for (const idx in this.fields) {
- this.buildField(this.fields[idx])
- }
- this.on('postsync', this.onPostSync)
- return this.form
- },
-
- buildField: function (field) {
- // field can be either a string like "option.name" or a full definition array,
- // like ['options.tilelayer.tms', {handler: 'CheckBox', helpText: 'TMS format'}]
- let type
- let helper
- let options
- if (Array.isArray(field)) {
- options = field[1] || {}
- field = field[0]
- } else {
- options = this.defaultOptions[this.getName(field)] || {}
- }
- type = options.handler || 'Input'
- if (typeof type === 'string' && L.FormBuilder[type]) {
- helper = new L.FormBuilder[type](this, field, options)
- } else {
- helper = new type(this, field, options)
- }
- this.helpers[field] = helper
- return helper
- },
-
- getter: function (field) {
- const path = field.split('.')
- let value = this.obj
- for (const sub of path) {
- try {
- value = value[sub]
- } catch {
- console.log(field)
- }
- }
- return value
- },
-
- setter: function (field, value) {
- const path = field.split('.')
- let obj = this.obj
- let what
- for (let i = 0, l = path.length; i < l; i++) {
- what = path[i]
- if (what === path[l - 1]) {
- if (typeof value === 'undefined') {
- delete obj[what]
- } else {
- obj[what] = value
- }
- } else {
- obj = obj[what]
- }
- }
- },
-
- restoreField: function (field) {
- const initial = this.helpers[field].initial
- this.setter(field, initial)
- },
-
- getName: (field) => {
- const fieldEls = field.split('.')
- return fieldEls[fieldEls.length - 1]
- },
-
- fetchAll: function () {
- for (const helper of Object.values(this.helpers)) {
- helper.fetch()
- }
- },
-
- syncAll: function () {
- for (const helper of Object.values(this.helpers)) {
- helper.sync()
- }
- },
-
- onPostSync: function (e) {
- if (e.helper.options.callback) {
- e.helper.options.callback.call(e.helper.options.callbackContext || this.obj, e)
- }
- if (this.options.callback) {
- this.options.callback.call(this.options.callbackContext || this.obj, e)
- }
- },
-})
-
-L.FormBuilder.Element = L.Evented.extend({
- initialize: function (builder, field, options) {
- this.builder = builder
- this.obj = this.builder.obj
- this.form = this.builder.form
- this.field = field
- L.setOptions(this, options)
- this.fieldEls = this.field.split('.')
- this.name = this.builder.getName(field)
- this.parentNode = this.getParentNode()
- this.buildLabel()
- this.build()
- this.buildHelpText()
- this.fireAndForward('helper:init')
- },
-
- fireAndForward: function (type, e = {}) {
- e.helper = this
- this.fire(type, e)
- this.builder.fire(type, e)
- if (this.obj.fire) this.obj.fire(type, e)
- },
-
- getParentNode: function () {
- return this.options.wrapper
- ? L.DomUtil.create(
- this.options.wrapper,
- this.options.wrapperClass || '',
- this.form
- )
- : this.form
- },
-
- get: function () {
- return this.builder.getter(this.field)
- },
-
- toHTML: function () {
- return this.get()
- },
-
- toJS: function () {
- return this.value()
- },
-
- sync: function () {
- this.fireAndForward('presync')
- this.set()
- this.fireAndForward('postsync')
- },
-
- set: function () {
- this.builder.setter(this.field, this.toJS())
- },
-
- getLabelParent: function () {
- return this.parentNode
- },
-
- getHelpTextParent: function () {
- return this.parentNode
- },
-
- buildLabel: function () {
- if (this.options.label) {
- this.label = L.DomUtil.create('label', '', this.getLabelParent())
- this.label.innerHTML = this.options.label
- }
- },
-
- buildHelpText: function () {
- if (this.options.helpText) {
- const container = L.DomUtil.create('small', 'help-text', this.getHelpTextParent())
- container.innerHTML = this.options.helpText
- }
- },
-
- fetch: () => {},
-
- finish: function () {
- this.fireAndForward('finish')
- },
-})
-
-L.FormBuilder.Textarea = L.FormBuilder.Element.extend({
- build: function () {
- this.input = L.DomUtil.create(
- 'textarea',
- this.options.className || '',
- this.parentNode
- )
- if (this.options.placeholder) this.input.placeholder = this.options.placeholder
- this.fetch()
- L.DomEvent.on(this.input, 'input', this.sync, this)
- L.DomEvent.on(this.input, 'keypress', this.onKeyPress, this)
- },
-
- fetch: function () {
- const value = this.toHTML()
- this.initial = value
- if (value) {
- this.input.value = value
- }
- },
-
- value: function () {
- return this.input.value
- },
-
- onKeyPress: function (e) {
- if (e.key === 'Enter' && (e.shiftKey || e.ctrlKey)) {
- L.DomEvent.stop(e)
- this.finish()
- }
- },
-})
-
-L.FormBuilder.Input = L.FormBuilder.Element.extend({
- build: function () {
- this.input = L.DomUtil.create(
- 'input',
- this.options.className || '',
- this.parentNode
- )
- this.input.type = this.type()
- this.input.name = this.name
- this.input._helper = this
- if (this.options.placeholder) {
- this.input.placeholder = this.options.placeholder
- }
- if (this.options.min !== undefined) {
- this.input.min = this.options.min
- }
- if (this.options.max !== undefined) {
- this.input.max = this.options.max
- }
- if (this.options.step) {
- this.input.step = this.options.step
- }
- this.fetch()
- L.DomEvent.on(this.input, this.getSyncEvent(), this.sync, this)
- L.DomEvent.on(this.input, 'keydown', this.onKeyDown, this)
- },
-
- fetch: function () {
- const value = this.toHTML() !== undefined ? this.toHTML() : null
- this.initial = value
- this.input.value = value
- },
-
- getSyncEvent: () => 'input',
-
- type: function () {
- return this.options.type || 'text'
- },
-
- value: function () {
- return this.input.value || undefined
- },
-
- onKeyDown: function (e) {
- if (e.key === 'Enter') {
- L.DomEvent.stop(e)
- this.finish()
- }
- },
-})
-
-L.FormBuilder.BlurInput = L.FormBuilder.Input.extend({
- getSyncEvent: () => 'blur',
-
- build: function () {
- L.FormBuilder.Input.prototype.build.call(this)
- L.DomEvent.on(this.input, 'focus', this.fetch, this)
- },
-
- finish: function () {
- this.sync()
- L.FormBuilder.Input.prototype.finish.call(this)
- },
-
- sync: function () {
- // Do not commit any change if user only clicked
- // on the field than clicked outside
- if (this.initial !== this.value()) {
- L.FormBuilder.Input.prototype.sync.call(this)
- }
- },
-})
-
-L.FormBuilder.IntegerMixin = {
- value: function () {
- return !isNaN(this.input.value) && this.input.value !== ''
- ? parseInt(this.input.value, 10)
- : undefined
- },
-
- type: () => 'number',
-}
-
-L.FormBuilder.IntInput = L.FormBuilder.Input.extend({
- includes: [L.FormBuilder.IntegerMixin],
-})
-
-L.FormBuilder.BlurIntInput = L.FormBuilder.BlurInput.extend({
- includes: [L.FormBuilder.IntegerMixin],
-})
-
-L.FormBuilder.FloatMixin = {
- value: function () {
- return !isNaN(this.input.value) && this.input.value !== ''
- ? parseFloat(this.input.value)
- : undefined
- },
-
- type: () => 'number',
-}
-
-L.FormBuilder.FloatInput = L.FormBuilder.Input.extend({
- options: {
- step: 'any',
- },
-
- includes: [L.FormBuilder.FloatMixin],
-})
-
-L.FormBuilder.BlurFloatInput = L.FormBuilder.BlurInput.extend({
- options: {
- step: 'any',
- },
-
- includes: [L.FormBuilder.FloatMixin],
-})
-
-L.FormBuilder.CheckBox = L.FormBuilder.Element.extend({
- build: function () {
- const container = L.DomUtil.create('div', 'checkbox-wrapper', this.parentNode)
- this.input = L.DomUtil.create('input', this.options.className || '', container)
- this.input.type = 'checkbox'
- this.input.name = this.name
- this.input._helper = this
- this.fetch()
- L.DomEvent.on(this.input, 'change', this.sync, this)
- },
-
- fetch: function () {
- this.initial = this.toHTML()
- this.input.checked = this.initial === true
- },
-
- value: function () {
- return this.input.checked
- },
-
- toHTML: function () {
- return [1, true].indexOf(this.get()) !== -1
- },
-})
-
-L.FormBuilder.Select = L.FormBuilder.Element.extend({
- selectOptions: [['value', 'label']],
-
- build: function () {
- this.select = L.DomUtil.create('select', '', this.parentNode)
- this.select.name = this.name
- this.validValues = []
- this.buildOptions()
- L.DomEvent.on(this.select, 'change', this.sync, this)
- },
-
- getOptions: function () {
- return this.options.selectOptions || this.selectOptions
- },
-
- fetch: function () {
- this.buildOptions()
- },
-
- buildOptions: function () {
- this.select.innerHTML = ''
- for (const option of this.getOptions()) {
- if (typeof option === 'string') this.buildOption(option, option)
- else this.buildOption(option[0], option[1])
- }
- },
-
- buildOption: function (value, label) {
- this.validValues.push(value)
- const option = L.DomUtil.create('option', '', this.select)
- option.value = value
- option.innerHTML = label
- if (this.toHTML() === value) {
- option.selected = 'selected'
- }
- },
-
- value: function () {
- if (this.select[this.select.selectedIndex])
- return this.select[this.select.selectedIndex].value
- },
-
- getDefault: function () {
- return this.getOptions()[0][0]
- },
-
- toJS: function () {
- const value = this.value()
- if (this.validValues.indexOf(value) !== -1) {
- return value
- }
- return this.getDefault()
- },
-})
-
-L.FormBuilder.IntSelect = L.FormBuilder.Select.extend({
- value: function () {
- return parseInt(L.FormBuilder.Select.prototype.value.apply(this), 10)
- },
-})
-
-L.FormBuilder.NullableBoolean = L.FormBuilder.Select.extend({
- selectOptions: [
- [undefined, 'inherit'],
- [true, 'yes'],
- [false, 'no'],
- ],
-
- toJS: function () {
- let value = this.value()
- switch (value) {
- case 'true':
- case true:
- value = true
- break
- case 'false':
- case false:
- value = false
- break
- default:
- value = undefined
- }
- return value
- },
-})
diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html
index f6aca61e..97473931 100644
--- a/umap/templates/umap/js.html
+++ b/umap/templates/umap/js.html
@@ -30,8 +30,6 @@
-
@@ -40,7 +38,6 @@
-
{% endautoescape %}