wip: internalize FormBuilder and move to module

This commit is contained in:
Yohan Boniface 2025-01-06 12:29:29 +01:00
parent ebae9a8cd0
commit b88a0cc49f
10 changed files with 1738 additions and 1729 deletions

View file

@ -47,7 +47,6 @@
"leaflet": "1.9.4", "leaflet": "1.9.4",
"leaflet-editable": "^1.3.0", "leaflet-editable": "^1.3.0",
"leaflet-editinosm": "0.2.3", "leaflet-editinosm": "0.2.3",
"leaflet-formbuilder": "0.2.10",
"leaflet-fullscreen": "1.0.2", "leaflet-fullscreen": "1.0.2",
"leaflet-hash": "0.2.1", "leaflet-hash": "0.2.1",
"leaflet-i18n": "0.3.5", "leaflet-i18n": "0.3.5",

View file

@ -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/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/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/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/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/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/ mkdir -p umap/static/umap/vendors/csv2geojson/ && cp -r node_modules/csv2geojson/csv2geojson.js umap/static/umap/vendors/csv2geojson/

View file

@ -4,6 +4,7 @@ import * as Icon from './rendering/icon.js'
import * as Utils from './utils.js' import * as Utils from './utils.js'
import { EXPORT_FORMATS } from './formatter.js' import { EXPORT_FORMATS } from './formatter.js'
import ContextMenu from './ui/contextmenu.js' import ContextMenu from './ui/contextmenu.js'
import { Form } from './form/builder.js'
export default class Browser { export default class Browser {
constructor(umap, leafletMap) { constructor(umap, leafletMap) {
@ -179,7 +180,7 @@ export default class Browser {
], ],
['options.inBbox', { handler: 'Switch', label: translate('Current map view') }], ['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(), callback: () => this.onFormChange(),
}) })
let filtersBuilder let filtersBuilder
@ -189,7 +190,7 @@ export default class Browser {
}) })
if (this._umap.properties.facetKey) { if (this._umap.properties.facetKey) {
fields = this._umap.facets.build() fields = this._umap.facets.build()
filtersBuilder = new L.FormBuilder(this._umap.facets, fields, { filtersBuilder = new Form(this._umap.facets, fields, {
callback: () => this.onFormChange(), callback: () => this.onFormChange(),
}) })
DomEvent.on(filtersBuilder.form, 'reset', () => { DomEvent.on(filtersBuilder.form, 'reset', () => {

View file

@ -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('<form></form>')
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()
}
}

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,7 @@ import {
uMapAlert as Alert, uMapAlert as Alert,
} from '../components/alerts/alert.js' } from '../components/alerts/alert.js'
import Orderable from './orderable.js' import Orderable from './orderable.js'
import { DataForm } from './form/builder.js'
export default class Umap extends ServerStored { export default class Umap extends ServerStored {
constructor(element, geojson) { constructor(element, geojson) {
@ -734,7 +735,7 @@ export default class Umap extends ServerStored {
const metadataFields = ['properties.name', 'properties.description'] const metadataFields = ['properties.name', 'properties.description']
DomUtil.createTitle(container, translate('Edit map details'), 'icon-caption') 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', className: 'map-metadata',
umap: this, umap: this,
}) })
@ -749,7 +750,7 @@ export default class Umap extends ServerStored {
'properties.permanentCredit', 'properties.permanentCredit',
'properties.permanentCreditBackground', 'properties.permanentCreditBackground',
] ]
const creditsBuilder = new U.FormBuilder(this, creditsFields, { umap: this }) const creditsBuilder = new DataForm(this, creditsFields, { umap: this })
credits.appendChild(creditsBuilder.build()) credits.appendChild(creditsBuilder.build())
this.editPanel.open({ content: container }) this.editPanel.open({ content: container })
} }
@ -770,7 +771,7 @@ export default class Umap extends ServerStored {
'properties.captionBar', 'properties.captionBar',
'properties.captionMenus', 'properties.captionMenus',
]) ])
const builder = new U.FormBuilder(this, UIFields, { umap: this }) const builder = new DataForm(this, UIFields, { umap: this })
const controlsOptions = DomUtil.createFieldset( const controlsOptions = DomUtil.createFieldset(
container, container,
translate('User interface options') translate('User interface options')
@ -793,7 +794,7 @@ export default class Umap extends ServerStored {
'properties.dashArray', 'properties.dashArray',
] ]
const builder = new U.FormBuilder(this, shapeOptions, { umap: this }) const builder = new DataForm(this, shapeOptions, { umap: this })
const defaultShapeProperties = DomUtil.createFieldset( const defaultShapeProperties = DomUtil.createFieldset(
container, container,
translate('Default shape properties') translate('Default shape properties')
@ -812,7 +813,7 @@ export default class Umap extends ServerStored {
'properties.slugKey', 'properties.slugKey',
] ]
const builder = new U.FormBuilder(this, optionsFields, { umap: this }) const builder = new DataForm(this, optionsFields, { umap: this })
const defaultProperties = DomUtil.createFieldset( const defaultProperties = DomUtil.createFieldset(
container, container,
translate('Default properties') translate('Default properties')
@ -830,7 +831,7 @@ export default class Umap extends ServerStored {
'properties.labelInteractive', 'properties.labelInteractive',
'properties.outlinkTarget', 'properties.outlinkTarget',
] ]
const builder = new U.FormBuilder(this, popupFields, { umap: this }) const builder = new DataForm(this, popupFields, { umap: this })
const popupFieldset = DomUtil.createFieldset( const popupFieldset = DomUtil.createFieldset(
container, container,
translate('Default interaction options') translate('Default interaction options')
@ -887,7 +888,7 @@ export default class Umap extends ServerStored {
container, container,
translate('Custom background') translate('Custom background')
) )
const builder = new U.FormBuilder(this, tilelayerFields, { umap: this }) const builder = new DataForm(this, tilelayerFields, { umap: this })
customTilelayer.appendChild(builder.build()) customTilelayer.appendChild(builder.build())
} }
@ -935,7 +936,7 @@ export default class Umap extends ServerStored {
['properties.overlay.tms', { handler: 'Switch', label: translate('TMS format') }], ['properties.overlay.tms', { handler: 'Switch', label: translate('TMS format') }],
] ]
const overlay = DomUtil.createFieldset(container, translate('Custom overlay')) 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()) overlay.appendChild(builder.build())
} }
@ -962,7 +963,7 @@ export default class Umap extends ServerStored {
{ handler: 'BlurFloatInput', placeholder: translate('max East') }, { 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()) limitBounds.appendChild(boundsBuilder.build())
const boundsButtons = DomUtil.create('div', 'button-bar half', limitBounds) const boundsButtons = DomUtil.create('div', 'button-bar half', limitBounds)
DomUtil.createButton( DomUtil.createButton(
@ -1027,7 +1028,7 @@ export default class Umap extends ServerStored {
{ handler: 'Switch', label: translate('Autostart when map is loaded') }, { handler: 'Switch', label: translate('Autostart when map is loaded') },
], ],
] ]
const slideshowBuilder = new U.FormBuilder(this, slideshowFields, { const slideshowBuilder = new DataForm(this, slideshowFields, {
callback: () => { callback: () => {
this.slideshow.load() this.slideshow.load()
// FIXME when we refactor formbuilder: this callback is called in a 'postsync' // FIXME when we refactor formbuilder: this callback is called in a 'postsync'
@ -1042,7 +1043,9 @@ export default class Umap extends ServerStored {
_editSync(container) { _editSync(container) {
const sync = DomUtil.createFieldset(container, translate('Real-time collaboration')) 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()) sync.appendChild(builder.build())
} }
@ -1459,7 +1462,7 @@ export default class Umap extends ServerStored {
const row = DomUtil.create('li', 'orderable', ul) const row = DomUtil.create('li', 'orderable', ul)
DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder')) DomUtil.createIcon(row, 'icon-drag', translate('Drag to reorder'))
datalayer.renderToolbox(row) datalayer.renderToolbox(row)
const builder = new U.FormBuilder( const builder = new DataForm(
datalayer, datalayer,
[['options.name', { handler: 'EditableText' }]], [['options.name', { handler: 'EditableText' }]],
{ className: 'umap-form-inline' } { className: 'umap-form-inline' }

View file

@ -446,3 +446,153 @@ export function eachElement(selector, callback) {
callback(el) 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',
]

File diff suppressed because it is too large Load diff

View file

@ -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
},
})

View file

@ -30,8 +30,6 @@
<script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}" <script src="{% static 'umap/vendors/fullscreen/Leaflet.fullscreen.min.js' %}"
defer></script> defer></script>
<script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script> <script src="{% static 'umap/vendors/toolbar/leaflet.toolbar.js' %}" defer></script>
<script src="{% static 'umap/vendors/formbuilder/Leaflet.FormBuilder.js' %}"
defer></script>
<script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}" <script src="{% static 'umap/vendors/measurable/Leaflet.Measurable.js' %}"
defer></script> defer></script>
<script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script> <script src="{% static 'umap/vendors/iconlayers/iconLayers.js' %}" defer></script>
@ -40,7 +38,6 @@
<script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}" <script src="{% static 'umap/vendors/simple-statistics/simple-statistics.min.js' %}"
defer></script> defer></script>
<script src="{% static 'umap/js/umap.core.js' %}" defer></script> <script src="{% static 'umap/js/umap.core.js' %}" defer></script>
<script src="{% static 'umap/js/umap.forms.js' %}" defer></script>
<script src="{% static 'umap/js/umap.controls.js' %}" defer></script> <script src="{% static 'umap/js/umap.controls.js' %}" defer></script>
<script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script> <script type="module" src="{% static 'umap/js/components/fragment.js' %}" defer></script>
{% endautoescape %} {% endautoescape %}