Skip to content

Commit

Permalink
[#2379] Reordered notifications order + forced focus on 1st element
Browse files Browse the repository at this point in the history
  • Loading branch information
jiromaykin committed Jul 4, 2024
1 parent 6389a61 commit 19637ed
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<div class="notifications notifications__errors">
<div class="notification notification--warning">
{% icon icon="warning_amber" icon_position="before" outlined=True %}
<div class="notification__content" tabindex="-1">
<div class="notification__content" role="alert" tabindex="-1">
<p class="utrecht-paragraph utrecht-paragraph--oip utrecht-paragraph--oip-compact">{{ message.message }}</p>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
</div>
{% endif %}

<div class="notification__content" tabindex="-1">
<div class="notification__content"
{% if type == "error" %}
role="alert"
{% elif type == "warning" %}
role="alert"
{% else %}
role="status"
{% endif %} tabindex="-1">
{% if title %}<h2 class="utrecht-heading-2">{{ title }}</h2>{% endif %}
{% if notification %}<p class="utrecht-paragraph">{{ notification }}</p>{% endif %}
{% if action %}{% button href=action text=action_text %}{% endif %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{% load notification_tags string_tags %}

<div class="notifications">
<section class="notifications">
{% for message in messages %}
{% with as_markdown=message.extra_tags|is_substring:"as_markdown" %}
{% with local_message=message.extra_tags|is_substring:"local_message" %}
Expand All @@ -14,4 +14,4 @@
{% endwith %}
{% endwith %}
{% endfor %}
</div>
</section>
4 changes: 2 additions & 2 deletions src/open_inwoner/conf/locale/nl/LC_MESSAGES/django.po
Original file line number Diff line number Diff line change
Expand Up @@ -6552,9 +6552,9 @@ msgstr "Zoekindex opnieuw opbouwen"
#: open_inwoner/search/tests/test_feedback.py:192
#: open_inwoner/search/views.py:158
msgid ""
"Thank you for your feedback. It will help us to improve our search engine"
"Thank you for your feedback, it will help us improve our search engine."
msgstr ""
"Dank u voor uw feedback, hiermee kunnen wij de omgeving verder verbeteren"
"Dank u voor uw feedback, hiermee kunnen wij de omgeving verder verbeteren."

#: open_inwoner/search/tests/test_logging.py:33 open_inwoner/search/views.py:63
#, python-brace-format
Expand Down
2 changes: 2 additions & 0 deletions src/open_inwoner/js/components/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import './header'
import './map'
import './message-file'
import { Notification } from './notifications'
import { NotificationsList } from './notifications/NotificationsList'
import './plans'
import './plan-preview'
import './questionnaire'
Expand Down Expand Up @@ -55,6 +56,7 @@ const elementWrappers = [
(elt) => new DisableContactFormButton(elt),
],
[Notification.selector, (elt) => new Notification(elt)],
[NotificationsList.selector, (elt) => new NotificationsList(elt)],
[AnchorMobile.selector, (elt) => new AnchorMobile(elt)],
[StatusAccordion.selector, (elt) => new StatusAccordion(elt)],
[FileInput.selector, (elt) => new FileInput(elt)],
Expand Down
40 changes: 40 additions & 0 deletions src/open_inwoner/js/components/notifications/NotificationsList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/**
* Surrounding Notifications class.
* @class
*/
export class NotificationsList {
static selector = '.notifications'
constructor(notificationContents) {
this.notificationContents = notificationContents
}

scrollToFirstNotification() {
if (this.notificationContents.length > 0) {
// Scroll to the first notification, since there could be multiple
this.notificationContents[0].scrollIntoView({
block: 'center',
behavior: 'smooth',
})

// Add a delay before setting focus for screen readers
setTimeout(() => {
// Set focus on the first notification content
this.notificationContents[0].focus()
}, 100) // Adjust the delay as necessary
}
}
}

// Start!

// Select all notification contents
const notificationContents = document.querySelectorAll('.notification__content')

// Create instances of NotificationsList for each matching element in the NodeList
document
.querySelectorAll(NotificationsList.selector)
.forEach((notifications) => new NotificationsList(notifications))

// Create ScrollManager instance and focus the first notification content
const scrollManager = new NotificationsList(notificationContents)
scrollManager.scrollToFirstNotification()
87 changes: 76 additions & 11 deletions src/open_inwoner/js/components/notifications/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Notification class.
* Single Notification class.
* @class
*/
export class Notification {
Expand All @@ -14,6 +14,7 @@ export class Notification {
this.node = node

this.bindEvents()
this.reorderNotifications()
}

/**
Expand All @@ -28,19 +29,24 @@ export class Notification {
* Scrolls to the notification content and sets focus.
*/
scrollToNotification() {
const notificationContent = this.node.querySelector(
'.notification__content'
const notificationContents = Array.from(
this.node.querySelectorAll('.notification__content')
)

if (notificationContent) {
// Scroll to each notification content
notificationContents.forEach((content) => {
// If errors are present, scroll and trigger the opened state
notificationContent.scrollIntoView({
content.scrollIntoView({
block: 'center',
behavior: 'smooth',
})
// Set focus for screen readers
notificationContent.focus()
}

// Add a delay before setting focus for screen readers
setTimeout(() => {
// Set focus for screen readers
content.focus()
}, 100) // Adjust the delay as necessary
})
}

/**
Expand All @@ -51,8 +57,6 @@ export class Notification {
e.preventDefault()
this.close()
})

this.scrollToNotification()
}

/**
Expand All @@ -61,11 +65,72 @@ export class Notification {
close() {
this.node.parentElement.removeChild(this.node)
}

/**
* Reorders notifications based on type.
*/
reorderNotifications() {
const typeOrder = ['error', 'warning', 'success', 'info']

// Get all notifications in the parent container
const notificationsContainer = document.querySelector('.notifications')
const notifications = Array.from(
notificationsContainer.querySelectorAll(Notification.selector)
)

// Sort notifications based on type order
notifications.sort((a, b) => {
const typeA = getTypeOrderIndex(a)
const typeB = getTypeOrderIndex(b)
return typeA - typeB
})

// Re-append sorted notifications to parent container
notifications.forEach((notification) =>
notificationsContainer.appendChild(notification)
)
}
}

/**
* Helper function to determine the order index of a notification type.
* @param {HTMLElement} notification - The notification element.
* @returns {number} - Order index of the notification type.
*/
function getTypeOrderIndex(notification) {
const type = getTypeFromNotification(notification)
const typeOrder = ['error', 'warning', 'success', 'info']
return typeOrder.indexOf(type)
}

/**
* Helper function to get the type of a notification.
* @param {HTMLElement} notification - The notification element.
* @returns {string} - Type of the notification.
*/
function getTypeFromNotification(notification) {
const classes = notification.classList
for (let i = 0; i < classes.length; i++) {
const cls = classes[i]
if (cls.startsWith('notification--')) {
return cls.replace('notification--', '')
}
}
return ''
}

// Start!

// Create a new Notification instance for each matching element in the NodeList
// Create instances of Notification for each matching element in the NodeList
document
.querySelectorAll(Notification.selector)
.forEach((notification) => new Notification(notification))

// Focus and scroll the notifications after reordering
setTimeout(() => {
const firstNotification = document.querySelector(Notification.selector)
if (firstNotification) {
const instance = new Notification(firstNotification)
instance.scrollToNotification()
}
}, 0)
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
color: var(--notification-color-text);
display: flex;
//align-items: end;
gap: var(--spacing-extra-large);
gap: var(--spacing-medium);
margin: var(--spacing-medium) 0;
padding: var(--spacing-large);
box-sizing: border-box;
Expand Down
2 changes: 1 addition & 1 deletion src/open_inwoner/search/tests/test_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,6 @@ def test_feedback_form_not_displayed_after_submit(self):
self.assertEqual(
message.message,
_(
"Thank you for your feedback. It will help us to improve our search engine"
"Thank you for your feedback, it will help us improve our search engine."
),
)
2 changes: 1 addition & 1 deletion src/open_inwoner/search/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ def form_valid(self, form):
self.request,
messages.SUCCESS,
_(
"Thank you for your feedback. It will help us to improve our search engine"
"Thank you for your feedback, it will help us improve our search engine."
),
)
redirect = furl(reverse("search:search"))
Expand Down

0 comments on commit 19637ed

Please sign in to comment.