Stephan Böni - Toast

Toast

Toasts per Popover

Mit der Popover API lassen sich alle Sorten von Overlays nativ einbinden, also praktisch ohne Javascript. Unter dem Begriff Overlays (deutsch: Überlagerungen) werden alle Arten von Dialoge, Dropdowns, Toasts, Slide-Ins, Fly-Ins, usw. zusammengefasst. Popovers sind nicht-modal. Sie können aber pseudo-modal sein, indem sie als <dialog>-Element eingebunden werden.

Wir fokusieren uns jedoch in diesem Beitrag auf den Toast. Toasts sind Benachrichtigungen, die üblicherweise unten links oder rechts eingeblendet werden und die Bedienbarkeit der Seite nicht unterbrechen, also non-modal sind. Wir definieren dazu das benutzerdefinierte Element <toast-notification>.

Erfolgsmeldungen

Wir unterscheiden drei Arten von Erfolgsmeldungen: Alles gut (success), Okay mit Warnungen (warning), Abgebrochen mit Fehler (error). Bei Klick auf den untenstehenden Button wird je eine generiert. Success- und Warning-Meldungen werden nach 4 Sekunden automatisch ausgeblendet.

Ich gehe davon aus, dass eine Aktion jegliche Art von Erfolgsmeldungen zurückgeben kann, auch mehrere gleichzeitig. Diese werden als verschachteltes Array übrergeben. Nachfolgend das Beispiel, das bei obigem Button generiert wird.

default.js // Load Toasts async function loadToasts() { const Toast = await loadModule('Toast'); const messages = {}; messages.success = 'test success'; messages.warning = 'test warning'; messages.error = 'test error'; Toast.show(messages); }

Die default.js wird ihrerseits in jeder HTML-Seite möglichst weit unten aufgerufen, z.B. direkt vor dem </body>-Tag.

Modul einbinden

Damit die default.js nicht überbordet, sollten eher seltener benötigte Aktionen in Klassenmodule ausgelagert werden. Klassenmodule müssen bei Bedarf in der default.js importiert werden.

default.js // Load Module async function loadModule(module) { const file = './modules/' + module.charAt(0).toLowerCase() + module.slice(1) + '.js'; const mod = await import(file); return new mod[module]; }

Ich sammle die Klassenmodule im Unterordner modules und nenne die Dateien wie die Klasse, aber mit Kleinbuchstaben beginnend. Die Klasse Toast befindet sich also in ./modules/toast.js.

Klasse Toast

Die Klasse Toast hat nur die eine Methode show, die ein oben genanntes Array mit Erfolgsmeldungen als Übergabewert erwartet.

./modules/toast.js export const Toast = class { show(messages) { // Define popover template const template = '<p></p>\ <button onclick="this.parentElement.hidePopover();" class="icon">\ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">\ <path d="m329.08-286.54-42.54-42.54L437.46-480 286.54-629.92l42.54-42.54L480-521.54l149.92-150.92 42.54 42.54L521.54-480l150.92 150.92-42.54 42.54L480-437.46 329.08-286.54Z"/>\ </svg>\ </button>'; // Loop over all messages for (const msgType in messages) { // Create toast const popover = document.createElement('toast-notification'); popover.popover = 'manual'; popover.classList.add(msgType); popover.classList.add('newest'); popover.innerHTML = template; const content = popover.querySelector('p'); content.textContent = messages[msgType]; document.querySelector('main').appendChild(popover); popover.showPopover(); popover.style.bottom = '16px'; const toastHeight = Math.round(popover.offsetHeight); // Move other toasts up const toasts = document.querySelectorAll("toast-notification"); for (const toast of toasts) { if (toast.classList.contains('newest')) { toast.classList.remove('newest'); } else { const prevValue = toast.style.bottom.replace("px", ""); const newValue = parseInt(prevValue) + toastHeight + 16; toast.style.bottom = `${newValue}px`; } } // Remove the toast again after 4 seconds setTimeout(() => { if (!popover.classList.contains('error')) { popover.hidePopover(); } }, 4000); // When the toast closes, move higher toasts down popover.addEventListener("toggle", (event) => { if (event.newState === "closed") { popover.classList.add('removed') const toastHeight = Math.round(popover.offsetHeight); const toasts = document.querySelectorAll("toast-notification"); let down = 0; for (let index = toasts.length - 1; index >= 0; index--) { const toast = toasts[index]; if (toast.classList.contains('removed')) { down = 1; } if (down == 1) { const prevValue = toast.style.bottom.replace("px", ""); const newValue = parseInt(prevValue) - toastHeight - 16; toast.style.bottom = `${newValue}px`; } } popover.remove(); } }); } } }

Du kannst die vollständige Toast-Klasse in einer optimierten Version hier herunterladen.

Styles

Jetzt braucht es nur noch etwas CSS.

default.css /* toast notification */ toast-notification { inset: auto 1.6rem -16rem auto; box-shadow: #9ea5a6 0 0.4rem 0.8rem; border: 0; border-radius: 0.8rem; padding: 0.8rem; transition: bottom 0.2s ease-out; display: flex; &.success { background-color: #bfc599; } &.warning { background-color: #e5ce99; } &.error { background-color: #d999a3; } &::backdrop { display: none; } p { flex-grow: 1; margin: 0 0.8rem; hyphens: auto; &::after { display: block; height: 0.2rem; margin-top: 0.6rem; } } }

Das wirst du optisch wohl noch etwas auf dein CSS anpassen müssen.

Einzeler Toast senden

Das war eigentlich schon alles. Wenn du einfach einen einzelnen Toast senden willst, kannst du die nachfolgende Ergänzung in der default.js hinzufügen.

default.js // Write Toast async function writeToast(type, text) { const Toast = await loadModule('Toast'); const messages = {}; messages[type] = text; Toast.show(messages); }

Nun kannst du im HTML eine Nachricht auslösen.

HTML <script> document.addEventListener('DOMContentLoaded', () => { writeToast('success', 'Das ist eine Erfolgsnachricht.'); }); </script>

Dran bleiben

Du hast es geschafft. Abonniere meine Benachrichtigungen, um weitere News und Anleitungen von mir zu erhalten.

Feed einbinden