Skip to content

Commit

Permalink
a11y: use role=alert for messages from Django and JS
Browse files Browse the repository at this point in the history
Also define a custom module+css for alerts (chore).
  • Loading branch information
davidbgk committed Feb 23, 2024
1 parent 76ed220 commit 9a2b4fd
Show file tree
Hide file tree
Showing 10 changed files with 125 additions and 28 deletions.
34 changes: 34 additions & 0 deletions umap/static/umap/alerts.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
[data-alert] {
box-sizing: border-box;
min-height: 46px;
line-height: 46px;
padding-left: 10px;
width: calc(100% - 500px);
position: absolute;
left: 250px; /* Keep save/cancel button accessible. */
right: 250px;
box-shadow: 0 1px 7px #999999;
background: none repeat scroll 0 0 rgba(20, 22, 23, 0.8);
font-weight: bold;
color: #fff;
font-size: 0.8em;
z-index: 1012;
border-radius: 2px;
visibility: visible;
top: 23px;
display: flex;
justify-content: space-between;
align-items: flex-start;
}
[data-alert][data-level="error"] {
background-color: #c60f13;
}
[data-alert] [data-close] {
color: #fff;
padding-right: 10px;
width: 100px;
line-height: 1;
margin: .5rem;
background-color: #202425;
font-size: .7rem;
}
54 changes: 54 additions & 0 deletions umap/static/umap/js/modules/alerts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
export default class Alerts {
constructor() {
this.alertNode = document.querySelector('[role="alert"]')
const observer = new MutationObserver(this._callback.bind(this))
observer.observe(this.alertNode, { childList: true })
// On initial page load, we want to display messages from Django.
Array.from(this.alertNode.children).forEach(this._display.bind(this))
}

_callback(mutationList, observer) {
for (const mutation of mutationList) {
this._display(
[...mutation.addedNodes].filter((item) => item.tagName === 'P').pop()
)
}
}

_display(alert) {
const duration = alert.dataset?.duration || 3000
const level = alert.dataset?.level || 'info'
const wrapper = document.createElement('div')
const alertHTML = alert.cloneNode(true).outerHTML
wrapper.innerHTML = `
<div data-level="${level}" data-alert data-toclose>
${alertHTML}
<button class="umap-close-link" type="button" data-close>
<i class="umap-close-icon"></i><span>${L._('Close')}</span>
</button>
</div>
`
const alertDiv = wrapper.firstElementChild
this.alertNode.after(alertDiv)
if (isFinite(duration)) {
setTimeout(() => {
alertDiv.remove()
}, duration)
}
}

add(message, level = 'info', duration = 3000) {
this.alertNode.innerHTML = `
<p data-level="${level}" data-duration="${duration}">
${message}
</p>
`
}
}

// TODISCUSS: this might be something we want somewhere else.
document.addEventListener('click', (event) => {
if (event.target.closest('[data-close]')) {
event.target.closest('[data-toclose]').remove()
}
})
12 changes: 11 additions & 1 deletion umap/static/umap/js/modules/global.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
import * as L from '../../vendors/leaflet/leaflet-src.esm.js'
import URLs from './urls.js'
import Alerts from './alerts.js'
import Browser from './browser.js'
import { Request, ServerRequest, RequestError, HTTPError, NOKError } from './request.js'
// Import modules and export them to the global scope.
// For the not yet module-compatible JS out there.

// Copy the leaflet module, it's expected by leaflet plugins to be writeable.
window.L = { ...L }
window.U = { URLs, Request, ServerRequest, RequestError, HTTPError, NOKError, Browser }
window.U = {
Alerts,
URLs,
Request,
ServerRequest,
RequestError,
HTTPError,
NOKError,
Browser,
}
2 changes: 1 addition & 1 deletion umap/static/umap/js/umap.controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -1272,7 +1272,7 @@ U.Search = L.PhotonSearch.extend({
if (latlng.isValid()) {
this.reverse.doReverse(latlng)
} else {
this.map.ui.alert({ content: 'Invalid latitude or longitude', mode: 'error' })
this.map.alerts.add(L._('Invalid latitude or longitude'), 'error')
}
return
}
Expand Down
5 changes: 1 addition & 4 deletions umap/static/umap/js/umap.features.js
Original file line number Diff line number Diff line change
Expand Up @@ -686,10 +686,7 @@ U.Marker = L.Marker.extend({
const builder = new U.FormBuilder(this, coordinatesOptions, {
callback: function () {
if (!this._latlng.isValid()) {
this.map.ui.alert({
content: L._('Invalid latitude or longitude'),
level: 'error',
})
this.map.alerts.add(L._('Invalid latitude or longitude'), 'error')
builder.resetField('_latlng.lat')
builder.resetField('_latlng.lng')
}
Expand Down
27 changes: 14 additions & 13 deletions umap/static/umap/js/umap.js
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ U.Map = L.Map.extend({
// After calling parent initialize, as we are doing initCenter our-selves
if (geojson.geometry) this.options.center = this.latLng(geojson.geometry)
this.urls = new U.URLs(this.options.urls)
this.alerts = new U.Alerts()

this.ui = new U.UI(this._container)
this.ui.on('dataloading', (e) => this.fire('dataloading', e))
Expand Down Expand Up @@ -1060,10 +1061,10 @@ U.Map = L.Map.extend({
const uri = this.urls.get('map_save', { map_id: this.options.umap_id })
const [data, response, error] = await this.server.post(uri, {}, formData)
if (!error) {
let duration = 3000,
alert = { content: L._('Map has been saved!'), level: 'info' }
let alertDuration = 3000
let alertMessage = L._('Map has been saved!')
if (!this.options.umap_id) {
alert.content = L._('Congratulations, your map has been created!')
alertMessage = L._('Congratulations, your map has been created!')
this.options.umap_id = data.id
this.permissions.setOptions(data.permissions)
this.permissions.commit()
Expand All @@ -1072,8 +1073,8 @@ U.Map = L.Map.extend({
data.permissions.anonymous_edit_url &&
this.options.urls.map_send_edit_link
) {
alert.duration = Infinity
alert.content =
alertDuration = Infinity
alertMessage =
L._(
'Your map has been created! As you are not logged in, here is your secret link to edit the map, please keep it safe:'
) + `<br>${data.permissions.anonymous_edit_url}`
Expand Down Expand Up @@ -1108,8 +1109,9 @@ U.Map = L.Map.extend({
if (history && history.pushState)
history.pushState({}, this.options.name, data.url)
else window.location = data.url
alert.content = data.info || alert.content
this.once('saved', () => this.ui.alert(alert))
this.once('saved', () => {
this.alerts.add(data.info || alertMessage, 'info', alertDuration)
})
this.ui.closePanel()
this.permissions.save()
}
Expand Down Expand Up @@ -1142,19 +1144,18 @@ U.Map = L.Map.extend({
},

star: async function () {
if (!this.options.umap_id)
return this.ui.alert({
content: L._('Please save the map first'),
level: 'error',
})
if (!this.options.umap_id) {
this.alerts.add(L._('Please save the map first'), 'error')
return
}
const url = this.urls.get('map_star', { map_id: this.options.umap_id })
const [data, response, error] = await this.server.post(url)
if (!error) {
this.options.starred = data.starred
let msg = data.starred
? L._('Map has been starred')
: L._('Map has been unstarred')
this.ui.alert({ content: msg, level: 'info' })
this.alerts.add(msg)
this.renderControls()
}
},
Expand Down
1 change: 1 addition & 0 deletions umap/static/umap/test/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@

<link rel="stylesheet" href="../../umap/font.css" />
<link rel="stylesheet" href="../../umap/base.css" />
<link rel="stylesheet" href="../../umap/alerts.css" />
<link rel="stylesheet" href="../../umap/content.css" />
<link rel="stylesheet" href="../../umap/nav.css" />
<link rel="stylesheet" href="../../umap/map.css" />
Expand Down
1 change: 1 addition & 0 deletions umap/templates/umap/css.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
href="{% static 'umap/vendors/iconlayers/iconLayers.css' %}" />
<link rel="stylesheet" href="{% static 'umap/font.css' %}" />
<link rel="stylesheet" href="{% static 'umap/base.css' %}" />
<link rel="stylesheet" href="{% static 'umap/alerts.css' %}" />
<link rel="stylesheet" href="{% static 'umap/content.css' %}" />
<link rel="stylesheet" href="{% static 'umap/nav.css' %}" />
<link rel="stylesheet" href="{% static 'umap/map.css' %}" />
Expand Down
15 changes: 7 additions & 8 deletions umap/templates/umap/map_init.html
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
{% load umap_tags %}

<div role="alert">
{% for m in messages %}
{# We have just one, but we need to loop, as for messages API #}
<p data-level="{{ m.tags }}" data-duration="100000">{{ m }}</p>
{% endfor %}
</div>
<div id="map"></div>
<!-- djlint:off -->
<script defer type="text/javascript">
window.addEventListener('DOMContentLoaded', (event) => {
U.MAP = new U.Map("map", {{ map_settings|notag|safe }})
{% for m in messages %}
{# We have just one, but we need to loop, as for messages API #}
U.MAP.ui.alert({
content: "{{ m }}",
level: "{{ m.tags }}",
duration: 100000
})
{% endfor %}
})
</script>
<!-- djlint:on -->
2 changes: 1 addition & 1 deletion umap/templates/umap/messages.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div class="wrapper">
<div class="row">
<div class="row" role="alert">
{% if messages %}
<ul class="messages">
{% for message in messages %}
Expand Down

0 comments on commit 9a2b4fd

Please sign in to comment.