From 0c6d045e3f55321bfcbc6693c0a32f18883c80da Mon Sep 17 00:00:00 2001 From: David Larlet Date: Tue, 12 Dec 2023 18:07:08 -0500 Subject: [PATCH 1/5] Template-based importer (vs. using L.DomUtil) --- umap/static/umap/js/umap.core.js | 10 +- umap/static/umap/js/umap.importer.js | 154 ++++++++++----------------- umap/templates/umap/map_init.html | 42 +++++++- 3 files changed, 100 insertions(+), 106 deletions(-) diff --git a/umap/static/umap/js/umap.core.js b/umap/static/umap/js/umap.core.js index 1dcda4b2f..2edba8637 100644 --- a/umap/static/umap/js/umap.core.js +++ b/umap/static/umap/js/umap.core.js @@ -598,12 +598,10 @@ U.Help = L.Class.extend({ return typeof this[name] === 'function' ? this[name]() : this[name] }, - button: function (container, entries, classname) { - const helpButton = L.DomUtil.createButton( - classname || 'umap-help-button', - container, - L._('Help') - ) + button: function (container, entries, classname, button) { + const helpButton = + button || + L.DomUtil.createButton(classname || 'umap-help-button', container, L._('Help')) if (entries) { L.DomEvent.on(helpButton, 'click', L.DomEvent.stop).on( helpButton, diff --git a/umap/static/umap/js/umap.importer.js b/umap/static/umap/js/umap.importer.js index 461932000..2bd03e1e5 100644 --- a/umap/static/umap/js/umap.importer.js +++ b/umap/static/umap/js/umap.importer.js @@ -1,124 +1,80 @@ -U.Importer = L.Class.extend({ - TYPES: ['geojson', 'csv', 'gpx', 'kml', 'osm', 'georss', 'umap'], +L.U.Importer = L.Class.extend({ initialize: function (map) { this.map = map this.presets = map.options.importPresets }, - build: function () { - this.container = L.DomUtil.create('div', 'umap-upload') - this.title = L.DomUtil.add('h3', '', this.container, L._('Import data')) - this.presetBox = L.DomUtil.create('div', 'formbox', this.container) - this.presetSelect = L.DomUtil.create('select', '', this.presetBox) - this.fileBox = L.DomUtil.create('div', 'formbox', this.container) - this.fileInput = L.DomUtil.element( - 'input', - { type: 'file', multiple: 'multiple', autofocus: true }, - this.fileBox - ) - this.map.ui.once('panel:closed', () => (this.fileInput.value = null)) - this.urlInput = L.DomUtil.element( - 'input', - { type: 'text', placeholder: L._('Provide an URL here') }, - this.container - ) - this.rawInput = L.DomUtil.element( - 'textarea', - { placeholder: L._('Paste your data here') }, - this.container - ) - this.typeLabel = L.DomUtil.add( - 'label', - '', - this.container, - L._('Choose the format of the data to import') - ) - this.layerLabel = L.DomUtil.add( - 'label', - '', - this.container, - L._('Choose the layer to import in') - ) - this.clearLabel = L.DomUtil.add( - 'label', - '', - this.container, - L._('Replace layer content') - ) - this.submitInput = L.DomUtil.element( - 'input', - { type: 'button', value: L._('Import'), className: 'button' }, - this.container - ) - this.map.help.button(this.typeLabel, 'importFormats') - this.typeInput = L.DomUtil.element('select', { name: 'format' }, this.typeLabel) - this.layerInput = L.DomUtil.element( - 'select', - { name: 'datalayer' }, - this.layerLabel - ) - this.clearFlag = L.DomUtil.element( - 'input', - { type: 'checkbox', name: 'clear' }, - this.clearLabel - ) + _buildDatalayerOptions: function (element) { let option this.map.eachDataLayerReverse((datalayer) => { if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) { const id = L.stamp(datalayer) - option = L.DomUtil.add('option', '', this.layerInput, datalayer.options.name) + option = L.DomUtil.add('option', '', element, datalayer.options.name) option.value = id } }) L.DomUtil.element( 'option', { value: '', textContent: L._('Import in a new layer') }, - this.layerInput - ) - L.DomUtil.element( - 'option', - { value: '', textContent: L._('Choose the data format') }, - this.typeInput + element ) - for (let i = 0; i < this.TYPES.length; i++) { - option = L.DomUtil.create('option', '', this.typeInput) - option.value = option.textContent = this.TYPES[i] - } + }, + + _buildPresetsOptions: function (element) { if (this.presets.length) { - const noPreset = L.DomUtil.create('option', '', this.presetSelect) + const presetBox = this.form.querySelector('#preset-box') + presetBox.removeAttribute('hidden') + const noPreset = L.DomUtil.create('option', '', element) noPreset.value = noPreset.textContent = L._('Choose a preset') - for (let j = 0; j < this.presets.length; j++) { + for (const preset of this.presets) { option = L.DomUtil.create('option', '', presetSelect) - option.value = this.presets[j].url - option.textContent = this.presets[j].label + option.value = preset.url + option.textContent = preset.label } - } else { - this.presetBox.style.display = 'none' } - L.DomEvent.on(this.submitInput, 'click', this.submit, this) - L.DomEvent.on( - this.fileInput, - 'change', - (e) => { - let type = '', - newType - for (let i = 0; i < e.target.files.length; i++) { - newType = L.Util.detectFileType(e.target.files[i]) - if (!type && newType) type = newType - if (type && newType !== type) { - type = '' - break - } + }, + + build: function () { + template = document.querySelector('#umap-upload') + this.form = template.content.firstElementChild.cloneNode(true) + this.presetSelect = this.form.querySelector('[name="preset-select"]') + this.fileInput = this.form.querySelector('[name="file-input"]') + this.urlInput = this.form.querySelector('[name="url-input"]') + this.rawInput = this.form.querySelector('[name="raw-input"]') + this.typeLabel = this.form.querySelector('#type-label') + const helpButton = this.typeLabel.querySelector('button') + this.map.help.button(this.typeLabel, 'importFormats', '', helpButton) + this.formatSelect = this.form.querySelector('[name="format"]') + this.layerSelect = this.form.querySelector('[name="datalayer"]') + this.clearFlag = this.form.querySelector('[name="clear"]') + this.submitInput = this.form.querySelector('[name="submit-input"]') + this._buildDatalayerOptions(this.layerSelect) + this._buildPresetsOptions(this.presetSelect) + + this.submitInput.addEventListener('click', this.submit.bind(this)) + this.fileInput.addEventListener('change', (e) => { + let type = '' + let newType + for (const file of e.target.files) { + newType = L.Util.detectFileType(file) + if (!type && newType) { + type = newType } - this.typeInput.value = type - }, - this - ) + if (type && newType !== type) { + type = '' + break + } + } + this.formatSelect.value = type + }) }, open: function () { - if (!this.container) this.build() - this.map.ui.openPanel({ data: { html: this.container }, className: 'dark' }) + if (!this.form) this.build() + this.map.ui.openPanel({ + data: { html: this.form }, + className: 'dark', + }) }, openFiles: function () { @@ -127,8 +83,8 @@ U.Importer = L.Class.extend({ }, submit: function () { - let type = this.typeInput.value - const layerId = this.layerInput[this.layerInput.selectedIndex].value + let type = this.formatSelect.value + const layerId = this.layerSelect[this.layerSelect.selectedIndex].value let layer if (type === 'umap') { this.map.once('postsync', this.map._setDefaultCenter) @@ -136,7 +92,7 @@ U.Importer = L.Class.extend({ if (layerId) layer = this.map.datalayers[layerId] if (layer && this.clearFlag.checked) layer.empty() if (this.fileInput.files.length) { - for (let i = 0, file; (file = this.fileInput.files[i]); i++) { + for (const file of this.fileInput.files) { this.map.processFileToImport(file, layer, type) } } else { diff --git a/umap/templates/umap/map_init.html b/umap/templates/umap/map_init.html index 91713b35d..398022e4a 100644 --- a/umap/templates/umap/map_init.html +++ b/umap/templates/umap/map_init.html @@ -1,5 +1,45 @@ -{% load umap_tags %} +{% load i18n umap_tags %}
+ + - diff --git a/umap/templates/umap/js.html b/umap/templates/umap/js.html index ca839ae1d..2f1221929 100644 --- a/umap/templates/umap/js.html +++ b/umap/templates/umap/js.html @@ -45,7 +45,6 @@ - From 76d30e8602b644e184b4b76f5d6a02e921b60399 Mon Sep 17 00:00:00 2001 From: David Larlet Date: Mon, 19 Feb 2024 16:09:37 -0500 Subject: [PATCH 5/5] More idiomatic JavaScript --- umap/static/umap/js/modules/importer.js | 135 ++++++++++++++---------- 1 file changed, 77 insertions(+), 58 deletions(-) diff --git a/umap/static/umap/js/modules/importer.js b/umap/static/umap/js/modules/importer.js index 15180126e..2135ab009 100644 --- a/umap/static/umap/js/modules/importer.js +++ b/umap/static/umap/js/modules/importer.js @@ -1,86 +1,105 @@ export default class Importer { constructor(map) { this.map = map - this.presets = map.options.importPresets } - #buildDatalayerOptions(layerSelect) { - let option - this.map.eachDataLayerReverse((datalayer) => { - if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) { - const id = L.stamp(datalayer) - option = L.DomUtil.add('option', '', layerSelect, datalayer.options.name) - option.value = id - } + open() { + if (!this.form) this._build() + this.map.ui.openPanel({ + data: { html: this.form }, + className: 'dark', }) - L.DomUtil.element( - 'option', - { value: '', textContent: L._('Import in a new layer') }, - layerSelect - ) } - #buildPresetsOptions(presetSelect) { - if (this.presets.length) { - const presetBox = this.form.querySelector('#preset-box') - presetBox.removeAttribute('hidden') - const noPreset = L.DomUtil.create('option', '', presetSelect) - noPreset.value = noPreset.textContent = L._('Choose a preset') - for (const preset of this.presets) { - option = L.DomUtil.create('option', '', presetSelect) - option.value = preset.url - option.textContent = preset.label - } - } + openFiles() { + this.open() + this.fileInput.showPicker() } - build() { + _build() { const template = document.querySelector('#umap-upload') this.form = template.content.firstElementChild.cloneNode(true) - this.presetSelect = this.form.querySelector('[name="preset-select"]') - this.fileInput = this.form.querySelector('[name="file-input"]') - this.map.ui.once('panel:closed', () => (this.fileInput.value = null)) + this.typeLabel = this.form.querySelector('#type-label') const helpButton = this.typeLabel.querySelector('button') this.map.help.button(this.typeLabel, 'importFormats', '', helpButton) - this.formatSelect = this.form.querySelector('[name="format"]') + this.layerSelect = this.form.querySelector('[name="datalayer"]') - this.submitInput = this.form.querySelector('[name="submit-input"]') - this.#buildDatalayerOptions(this.layerSelect) - this.#buildPresetsOptions(this.presetSelect) + this._buildDatalayerOptions(this.layerSelect) + this.presetSelect = this.form.querySelector('[name="preset-select"]') + this._buildPresetsOptions(this.presetSelect) - this.submitInput.addEventListener('click', this.submit.bind(this)) - this.fileInput.addEventListener('change', (e) => { - let type = '' - let newType - for (const file of e.target.files) { - newType = L.Util.detectFileType(file) - if (!type && newType) { - type = newType - } - if (type && newType !== type) { - type = '' - break - } + this.fileInput = this.form.querySelector('[name="file-input"]') + this.formatSelect = this.form.querySelector('[name="format"]') + + this._connectedCallback() + } + + _buildDatalayerOptions(layerSelect) { + const options = [] + this.map.eachDataLayerReverse((datalayer) => { + if (datalayer.isLoaded() && !datalayer.isRemoteLayer()) { + options.push( + `` + ) } - this.formatSelect.value = type }) + options.push(``) + layerSelect.innerHTML = options.join('') } - open() { - if (!this.form) this.build() - this.map.ui.openPanel({ - data: { html: this.form }, - className: 'dark', - }) + _buildPresetsOptions(presetSelect) { + const presets = this.map.options.importPresets + if (!presets.length) return + const options = [] + presetSelect.parentElement.removeAttribute('hidden') + options.push( + `` + ) + for (const preset of presets) { + options.push(``) + } + presetSelect.innerHTML = options.join('') } - openFiles() { - this.open() - this.fileInput.showPicker() + _connectedCallback() { + const controller = new AbortController() + const signal = controller.signal + this.form + .querySelector('[name="submit-input"]') + .addEventListener('click', this._submit.bind(this), { signal }) + + this.fileInput.addEventListener( + 'change', + (e) => { + let type = '' + let newType + for (const file of e.target.files) { + newType = L.Util.detectFileType(file) + if (!type && newType) { + type = newType + } + if (type && newType !== type) { + type = '' + break + } + } + this.formatSelect.value = type + }, + { signal } + ) + + this.map.ui.once( + 'panel:closed', + () => { + this.fileInput.value = null + controller.abort() + }, + { signal } + ) } - submit() { + _submit() { const urlInputValue = this.form.querySelector('[name="url-input"]').value const rawInputValue = this.form.querySelector('[name="raw-input"]').value const clearFlag = this.form.querySelector('[name="clear"]')