96 lines
3.9 KiB
JavaScript
96 lines
3.9 KiB
JavaScript
|
|
// Bootstrap helpers
|
|
(() => {
|
|
'use strict';
|
|
|
|
// 1) Show Flask flash messages as Bootstrap toasts
|
|
const flashes = document.getElementById('_flash_msgs');
|
|
if (flashes) {
|
|
try{
|
|
const msgs = JSON.parse(flashes.dataset.msgs || "[]");
|
|
const stack = document.getElementById('toast-stack');
|
|
msgs.forEach((m) => {
|
|
const el = document.createElement('div');
|
|
el.className = 'toast align-items-center text-bg-primary border-0';
|
|
el.role = 'alert'; el.ariaLive = 'assertive'; el.ariaAtomic = 'true';
|
|
el.innerHTML = `<div class="d-flex">
|
|
<div class="toast-body"><i class="bi bi-info-circle me-2"></i>${m}</div>
|
|
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Zamknij"></button>
|
|
</div>`;
|
|
stack.appendChild(el);
|
|
const t = new bootstrap.Toast(el, { delay: 3500 });
|
|
t.show();
|
|
});
|
|
}catch(e){ console.warn('Toast parse error', e); }
|
|
}
|
|
|
|
// 2) Client-side validation (Bootstrap)
|
|
const forms = document.querySelectorAll('form');
|
|
Array.from(forms).forEach(form => {
|
|
form.addEventListener('submit', event => {
|
|
if (!form.checkValidity()) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
} else {
|
|
// 3) Micro-loader on submit
|
|
const submitBtn = form.querySelector('[type="submit"]');
|
|
if (submitBtn && !submitBtn.dataset.loading) {
|
|
submitBtn.dataset.loading = '1';
|
|
submitBtn.disabled = true;
|
|
const original = submitBtn.innerHTML;
|
|
submitBtn.dataset.original = original;
|
|
submitBtn.innerHTML = `<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> Przetwarzanie...`;
|
|
}
|
|
}
|
|
form.classList.add('was-validated');
|
|
}, false);
|
|
});
|
|
|
|
// 4) Auto-accordion for long forms:
|
|
// If a form contains multiple H2/H3 sections, group content between them.
|
|
const targetForms = document.querySelectorAll('form');
|
|
targetForms.forEach(form => {
|
|
const headers = Array.from(form.querySelectorAll(':scope h2, :scope h3')).filter(h => h.textContent.trim().length>0);
|
|
if (headers.length >= 2) {
|
|
const acc = document.createElement('div');
|
|
acc.className = 'accordion my-3'; acc.id = 'autoAccordion-' + Math.random().toString(36).slice(2);
|
|
let startIdx = 0;
|
|
headers.forEach((h, i) => {
|
|
const itemId = acc.id + '-item-' + i;
|
|
const headerId = itemId + '-hdr';
|
|
const bodyId = itemId + '-body';
|
|
const item = document.createElement('div'); item.className = 'accordion-item bg-body';
|
|
const btnText = h.textContent.trim();
|
|
// gather content until next header or end
|
|
const chunk = [];
|
|
let el = h.nextElementSibling;
|
|
while (el && !headers.includes(el)) {
|
|
chunk.push(el);
|
|
el = el.nextElementSibling;
|
|
}
|
|
h.remove();
|
|
const bodyInner = document.createElement('div');
|
|
chunk.forEach(c => bodyInner.appendChild(c));
|
|
item.innerHTML = `
|
|
<h2 class="accordion-header" id="${headerId}">
|
|
<button class="accordion-button ${i>0 ? 'collapsed' : ''}" type="button" data-bs-toggle="collapse" data-bs-target="#${bodyId}" aria-expanded="${i===0?'true':'false'}" aria-controls="${bodyId}">
|
|
${btnText}
|
|
</button>
|
|
</h2>
|
|
<div id="${bodyId}" class="accordion-collapse collapse ${i===0?'show':''}" aria-labelledby="${headerId}" data-bs-parent="#${acc.id}">
|
|
<div class="accordion-body"></div>
|
|
</div>`;
|
|
item.querySelector('.accordion-body').appendChild(bodyInner);
|
|
acc.appendChild(item);
|
|
});
|
|
// Insert at top of form
|
|
form.prepend(acc);
|
|
}
|
|
});
|
|
|
|
// Focus first control
|
|
const first = document.querySelector('form input, form select, form textarea');
|
|
if (first) first.focus({preventScroll:true});
|
|
|
|
})();
|