Skip to main content

max / makenotwork

3.9 KB · 87 lines History Blame Raw
1 (function() {
2 'use strict';
3
4 // Handle browser back/forward: reload the step content via HTMX
5 window.addEventListener('popstate', function() {
6 var stepArea = document.getElementById('wizard-step');
7 if (!stepArea) return;
8 // Let HTMX handle the URL change by fetching the current URL's step content
9 var path = window.location.pathname;
10 if (path.indexOf('/step/') !== -1) {
11 htmx.ajax('GET', path, { target: '#wizard-step', swap: 'innerHTML' });
12 }
13 });
14
15 // After HTMX swaps, re-attach the OOB sidebar update.
16 //
17 // Settle rather than swap: htmx 4 fires `htmx:after:swap` on the element
18 // that made the request, and every wizard step posts from a form inside
19 // `#wizard-step`, which the swap replaces. That form is detached by the
20 // time the event fires, so nothing reaches `document`. Settle fires on the
21 // swap target instead.
22 document.addEventListener('htmx:after:settle', function(event) {
23 // The step_nav.html is included at the top of each partial,
24 // so the sidebar nav gets updated via include (not OOB).
25 // We need to move the nav from inside #wizard-step to the sidebar.
26 var wizardStep = document.getElementById('wizard-step');
27 if (!wizardStep) return;
28
29 var inlineNav = wizardStep.querySelector('#wizard-nav');
30 if (!inlineNav) return;
31
32 var sidebar = document.querySelector('.wizard-sidebar');
33 if (!sidebar) return;
34
35 var existingNav = sidebar.querySelector('#wizard-nav');
36 if (existingNav) {
37 existingNav.replaceWith(inlineNav);
38 } else {
39 sidebar.appendChild(inlineNav);
40 }
41 });
42 // Custom HTML5 validation messages for wizard forms.
43 // Replaces browser defaults ("Please fill out this field") with
44 // field-specific messages using data-* attributes or fallback rules.
45 function applyCustomValidation(root) {
46 var inputs = root.querySelectorAll('input[required], input[minlength], input[pattern], textarea[minlength]');
47 inputs.forEach(function(input) {
48 input.addEventListener('invalid', function(e) {
49 var v = e.target.validity;
50 var label = (e.target.closest('.form-group')?.querySelector('label')?.textContent || '').trim();
51 var custom = e.target.dataset.validationMessage;
52
53 if (custom) {
54 e.target.setCustomValidity(custom);
55 } else if (v.valueMissing) {
56 e.target.setCustomValidity(label ? label + ' is required' : 'This field is required');
57 } else if (v.tooShort) {
58 var min = e.target.getAttribute('minlength');
59 e.target.setCustomValidity('Must be at least ' + min + ' characters');
60 } else if (v.tooLong) {
61 var max = e.target.getAttribute('maxlength');
62 e.target.setCustomValidity('Must be ' + max + ' characters or fewer');
63 } else if (v.patternMismatch) {
64 e.target.setCustomValidity(e.target.title || 'Please use the expected format');
65 } else if (v.typeMismatch) {
66 e.target.setCustomValidity('Please enter a valid ' + (e.target.type || 'value'));
67 } else {
68 e.target.setCustomValidity('');
69 }
70 });
71 // Clear custom message on input so re-validation works
72 input.addEventListener('input', function() {
73 this.setCustomValidity('');
74 });
75 });
76 }
77
78 // Apply to initial page content
79 applyCustomValidation(document.body);
80
81 // Re-apply after HTMX swaps new wizard steps in. Settle, for the reason
82 // given above.
83 document.addEventListener('htmx:after:settle', function(event) {
84 applyCustomValidation(event.target);
85 });
86 })();
87