chore: refactor web components with templates

This commit is contained in:
David Larlet 2024-06-08 09:16:56 -04:00
parent cc773e7feb
commit 6bbdec49bf
No known key found for this signature in database
GPG key ID: 3E2953A359E7E7BD
4 changed files with 119 additions and 77 deletions

View file

@ -4,7 +4,7 @@
@import "{% static 'umap/js/components/alerts/alert.css' %}";
</style>
<umap-alert hidden>
<template id="umap-alert-template">
<div role="dialog">
<div>
<p role="alert"></p>
@ -13,9 +13,11 @@
<i class="icon icon-16 icon-close"></i>
</button>
</div>
</umap-alert>
</template>
<umap-alert-creation hidden>
<umap-alert></umap-alert>
<template id="umap-alert-creation-template">
<div role="dialog">
<div>
<p role="alert"></p>
@ -37,15 +39,17 @@
<i class="icon icon-16 icon-close"></i>
</button>
</div>
</umap-alert-creation>
</template>
<umap-alert-choice hidden>
<umap-alert-creation></umap-alert-creation>
<template id="umap-alert-choice-template">
<div role="dialog">
<div>
<p role="alert"></p>
<div id="choice-wrapper">
<form>
<a href="" onclick="document.url" target="_blank">{% translate "See their edits in another tab" %}</a>
<a href="#" onclick="document.url" target="_blank">{% translate "See their edits in another tab" %}</a>
<input id="your-changes" type="submit" value="{% translate "Keep your changes and loose theirs" %}">
<input id="their-changes" type="submit" value="{% translate "Keep their changes and loose yours" %}">
</form>
@ -55,15 +59,18 @@
<i class="icon icon-16 icon-close"></i>
</button>
</div>
</umap-alert-choice>
</template>
<umap-alert-choice></umap-alert-choice>
<script type="module">
import { register } from '{% static 'umap/js/components/base.js' %}'
import {
uMapAlert,
uMapAlertCreation,
uMapAlertChoice
} from '{% static 'umap/js/components/alerts/alert.js' %}'
customElements.define('umap-alert', uMapAlert)
customElements.define('umap-alert-creation', uMapAlertCreation)
customElements.define('umap-alert-choice', uMapAlertChoice)
register(uMapAlert, 'umap-alert')
register(uMapAlertCreation, 'umap-alert-creation')
register(uMapAlertChoice, 'umap-alert-choice')
</script>

View file

@ -1,43 +1,57 @@
class uMapAlert extends HTMLElement {
import { uMapElement } from '../base.js'
class uMapAlert extends uMapElement {
static get observedAttributes() {
return ['open']
}
attributeChangedCallback(name, oldValue, newValue) {
switch (name) {
case 'open':
newValue === 'open' ? this._show() : this._hide()
break
}
}
static info(message, duration = 5000) {
const event = new CustomEvent('umap:alert', {
bubbles: true,
cancelable: true,
detail: { message, duration },
})
document.dispatchEvent(event)
uMapAlert.emit('alert', { message, duration })
}
// biome-ignore lint/style/useNumberNamespace: Number.Infinity returns undefined by default
static error(message, duration = Infinity) {
const event = new CustomEvent('umap:alert', {
bubbles: true,
cancelable: true,
detail: { level: 'error', message, duration },
})
document.dispatchEvent(event)
uMapAlert.emit('alert', { level: 'error', message, duration })
}
constructor() {
super()
this._hide()
this.container = this.querySelector('[role="dialog"]')
this.element = this.container.querySelector('[role="alert"]')
}
_hide() {
this.setAttribute('hidden', 'hidden')
this.removeAttribute('open')
}
_show() {
this.removeAttribute('hidden')
}
_displayAlert(detail) {
const { level = 'info', duration = 5000, message = '' } = detail
_handleClose() {
this.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
this._hide()
}
})
}
onAlert(event) {
const { level = 'info', duration = 5000, message = '' } = event.detail
this.container.dataset.level = level
this.container.dataset.duration = duration
this.element.textContent = message
this._show()
this.setAttribute('open', 'open')
if (Number.isFinite(duration)) {
setTimeout(() => {
this._hide()
@ -46,14 +60,8 @@ class uMapAlert extends HTMLElement {
}
connectedCallback() {
this.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
this._hide()
}
})
document.addEventListener('umap:alert', (event) => {
this._displayAlert(event.detail)
})
this._handleClose()
this.listen('alert')
}
}
@ -65,12 +73,7 @@ class uMapAlertCreation extends uMapAlert {
editLink = undefined,
sendLink = undefined
) {
const event = new CustomEvent('umap:alert-creation', {
bubbles: true,
cancelable: true,
detail: { message, duration, editLink, sendLink },
})
document.dispatchEvent(event)
uMapAlertCreation.emit('alertCreation', { message, duration, editLink, sendLink })
}
constructor() {
@ -79,15 +82,15 @@ class uMapAlertCreation extends uMapAlert {
this.formWrapper = this.container.querySelector('#form-wrapper')
}
_displayCreationAlert(detail) {
onAlertCreation(event) {
const {
level = 'info',
duration = 5000,
message = '',
editLink = undefined,
sendLink = undefined,
} = detail
uMapAlert.prototype._displayAlert.call(this, { level, duration, message })
} = event.detail
uMapAlert.prototype.onAlert.call(this, { detail: { level, duration, message } })
this.linkWrapper.querySelector('input[type="url"]').value = editLink
const button = this.linkWrapper.querySelector('input[type="button"]')
button.addEventListener('click', (event) => {
@ -102,21 +105,15 @@ class uMapAlertCreation extends uMapAlert {
event.preventDefault()
const formData = new FormData(form)
const server = new U.ServerRequest()
this._hide()
this.removeAttribute('open')
await server.post(sendLink, {}, formData)
})
}
}
connectedCallback() {
this.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
this._hide()
}
})
document.addEventListener('umap:alert-creation', (event) => {
this._displayCreationAlert(event.detail)
})
this._handleClose()
this.listen('alertCreation')
}
}
@ -126,12 +123,7 @@ class uMapAlertChoice extends uMapAlert {
// biome-ignore lint/style/useNumberNamespace: Number.Infinity returns undefined by default
duration = Infinity
) {
const event = new CustomEvent('umap:alert-choice', {
bubbles: true,
cancelable: true,
detail: { level: 'error', message, duration },
})
document.dispatchEvent(event)
uMapAlertChoice.emit('alertChoice', { level: 'error', message, duration })
}
constructor() {
@ -139,38 +131,27 @@ class uMapAlertChoice extends uMapAlert {
this.choiceWrapper = this.container.querySelector('#choice-wrapper')
}
_displayChoiceAlert(detail) {
const { level = 'info', duration = 5000, message = '' } = detail
uMapAlert.prototype._displayAlert.call(this, { level, duration, message })
onAlertChoice(event) {
const { level = 'info', duration = 5000, message = '' } = event.detail
uMapAlert.prototype.onAlert.call(this, { detail: { level, duration, message } })
const form = this.choiceWrapper.querySelector('form')
form.addEventListener('submit', (event) => {
event.preventDefault()
switch (event.submitter.id) {
case 'your-changes':
document.dispatchEvent(
new CustomEvent('umap:alert-choice-override', {
bubbles: true,
cancelable: true,
})
)
uMapAlertChoice.emit('alertChoiceOverride')
break
case 'their-changes':
window.location.reload()
break
}
this._hide()
this.removeAttribute('open')
})
}
connectedCallback() {
this.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
this._hide()
}
})
document.addEventListener('umap:alert-choice', (event) => {
this._displayChoiceAlert(event.detail)
})
this._handleClose()
this.listen('alertChoice')
}
}

View file

@ -0,0 +1,54 @@
export class uMapElement extends HTMLElement {
static EVENT_PREFIX = 'umap'
static emit(type, detail = {}) {
const event = new CustomEvent(`${uMapElement.EVENT_PREFIX}:${type}`, {
bubbles: true,
cancelable: true,
detail: detail,
})
return document.dispatchEvent(event)
}
/**
* Retrieves a clone of the content template either using the `template`
* attribute or an id mathing the name of the component:
*
* `umap-alert` component => `umap-alert-template` template id lookup.
*/
get template() {
return document
.getElementById(this.getAttribute('template') || `${this.localName}-template`)
.content.cloneNode(true)
}
constructor() {
super()
this.append(this.template)
}
/**
* Special method which allows to easily listen to events
* and have automated event to component method binding.
*
* For instance listening to `alert` will then call `onAlert`.
*/
handleEvent(event) {
event.preventDefault()
// From `umap:alert` to `alert`.
const eventName = event.type.replace(`${uMapElement.EVENT_PREFIX}:`, '')
// From `alert` event type to `onAlert` call against that class.
this[`on${eventName.charAt(0).toUpperCase() + eventName.slice(1)}`](event)
}
listen(eventName) {
// Using `this` as a listener will call `handleEvent` under the hood.
document.addEventListener(`${uMapElement.EVENT_PREFIX}:${eventName}`, this)
}
}
export function register(klass, name) {
if ('customElements' in globalThis && !customElements.get(name)) {
customElements.define(name, klass)
}
}

View file

@ -1711,7 +1711,7 @@ U.DataLayer = L.Evented.extend({
'This situation is tricky, you have to choose carefully which version is pertinent.'
)
)
document.addEventListener('umap:alert-choice-override', async (event) => {
document.addEventListener('alertChoiceOverride', async (event) => {
await this._trySave(url, {}, formData)
})
}