|
3 | 3 | // re-processed during htmx hx-boost body swaps, which means the event |
4 | 4 | // listeners registered here persist across page navigations. |
5 | 5 |
|
6 | | -function showToast(message, level) { |
7 | | - level = level || 'success'; |
8 | | - var container = document.getElementById('toast-container'); |
9 | | - var wrapper = document.createElement('div'); |
10 | | - wrapper.className = 'toast align-items-center text-bg-' + level + ' border-0 show'; |
11 | | - wrapper.setAttribute('role', 'alert'); |
12 | | - wrapper.setAttribute('aria-atomic', 'true'); |
13 | | - wrapper.innerHTML = |
14 | | - '<div class="d-flex">' + |
15 | | - '<div class="toast-body">' + message + '</div>' + |
16 | | - '<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>' + |
17 | | - '</div>'; |
18 | | - container.appendChild(wrapper); |
19 | | - setTimeout(function() { wrapper.remove(); }, 5000); |
20 | | -} |
21 | | - |
22 | | -// For HTMX error responses, extract and apply OOB swaps (toasts) without |
23 | | -// touching the main target. We parse the response HTML, find elements with |
24 | | -// hx-swap-oob, and swap them in manually via htmx.process(). |
25 | | -document.body.addEventListener('htmx:beforeSwap', function(evt) { |
26 | | - if (evt.detail.xhr.status >= 400) { |
27 | | - evt.detail.shouldSwap = false; |
28 | | - evt.detail.isError = false; |
29 | | - var responseText = evt.detail.xhr.responseText; |
30 | | - if (responseText) { |
31 | | - var doc = new DOMParser().parseFromString(responseText, 'text/html'); |
32 | | - var oobElements = doc.querySelectorAll('[hx-swap-oob]'); |
33 | | - oobElements.forEach(function(el) { |
34 | | - var targetId = el.getAttribute('id'); |
35 | | - if (targetId) { |
36 | | - var existing = document.getElementById(targetId); |
37 | | - if (existing) { |
38 | | - existing.replaceWith(el); |
39 | | - htmx.process(el); |
40 | | - } |
41 | | - } |
42 | | - }); |
43 | | - } |
| 6 | +// Configure HTMX to process response bodies on error status codes so that |
| 7 | +// OOB-swapped toasts are applied. swapOverride:'none' ensures the main |
| 8 | +// target is left untouched while OOB elements are still processed. |
| 9 | +document.body.addEventListener('htmx:configRequest', function() { |
| 10 | + if (!htmx.config.responseHandling.find(function(r) { return r.code === '400'; })) { |
| 11 | + htmx.config.responseHandling = [ |
| 12 | + { code: '204', swap: false }, |
| 13 | + { code: '[23]..', swap: true }, |
| 14 | + { code: '[45]..', swap: true, error: false, swapOverride: 'none' }, |
| 15 | + ]; |
44 | 16 | } |
45 | | -}); |
| 17 | +}, { once: true }); |
46 | 18 |
|
47 | | -// Read flash cookie on page load |
48 | | -(function() { |
49 | | - var raw = document.cookie.split('; ').find(function(c) { return c.startsWith('flash_message='); }); |
50 | | - if (!raw) return; |
51 | | - var value = decodeURIComponent(raw.split('=').slice(1).join('=')); |
52 | | - document.cookie = 'flash_message=; Max-Age=0; path=/'; |
53 | | - try { |
54 | | - var flash = JSON.parse(value); |
55 | | - if (flash && flash.message) showToast(flash.message, flash.level); |
56 | | - } catch(e) {} |
57 | | -})(); |
58 | 19 |
|
59 | 20 | // Global handler: when a server response includes HX-Trigger: modalDismiss, |
60 | 21 | // clean up any Bootstrap modal backdrop left behind by OOB swaps that |
|
0 commit comments