Quelltext des aktuellen Standes
/* ============================================
Adressbuch (Contacts) – UI-Logik
Modals (mit Fokus-Falle), View-Toggle,
dynamische Kontaktkanaele, Loesch-Bestaetigung.
Keine Inline-Scripts in den Templates.
============================================ */
(function () {
'use strict';
var FOCUSABLE = 'a[href], button:not([disabled]), input:not([disabled]):not([type="hidden"]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])';
var lastFocused = null;
function getCsrf() {
var meta = document.querySelector('meta[name="csrf-token"]');
return meta ? meta.getAttribute('content') : '';
}
/* ---- Modals ---- */
function openModal(id) {
var modal = document.getElementById(id);
if (!modal) return;
lastFocused = document.activeElement;
modal.hidden = false;
document.body.style.overflow = 'hidden';
var first = modal.querySelector(FOCUSABLE);
if (first) first.focus();
}
function closeModal(modal) {
if (typeof modal === 'string') modal = document.getElementById(modal);
if (!modal) return;
modal.hidden = true;
if (!document.querySelector('.cb-modal:not([hidden])')) {
document.body.style.overflow = '';
}
if (lastFocused && typeof lastFocused.focus === 'function') {
lastFocused.focus();
lastFocused = null;
}
}
function trapFocus(e) {
if (e.key !== 'Tab') return;
var modal = document.querySelector('.cb-modal:not([hidden])');
if (!modal) return;
var nodes = Array.prototype.slice.call(modal.querySelectorAll(FOCUSABLE))
.filter(function (el) { return el.offsetParent !== null; });
if (!nodes.length) return;
var firstEl = nodes[0];
var lastEl = nodes[nodes.length - 1];
if (e.shiftKey && document.activeElement === firstEl) {
e.preventDefault();
lastEl.focus();
} else if (!e.shiftKey && document.activeElement === lastEl) {
e.preventDefault();
firstEl.focus();
}
}
document.addEventListener('click', function (e) {
var opener = e.target.closest('[data-modal-open]');
if (opener) {
e.preventDefault();
var modalId = opener.getAttribute('data-modal-open');
/* Einzel-Verschieben: nur den angeklickten Kontakt vorauswaehlen */
var moveId = opener.getAttribute('data-move-contact');
if (moveId) {
var moveModal = document.getElementById(modalId);
if (moveModal) {
moveModal.querySelectorAll('input[name="contact_ids[]"]').forEach(function (cb) {
cb.checked = cb.value === moveId;
});
var allCb = moveModal.querySelector('[data-check-all]');
if (allCb) allCb.checked = false;
}
}
openModal(modalId);
return;
}
var closer = e.target.closest('[data-close]');
if (closer) {
e.preventDefault();
closeModal(closer.closest('.cb-modal'));
}
});
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
var open = document.querySelector('.cb-modal:not([hidden])');
if (open) closeModal(open);
} else {
trapFocus(e);
}
});
/* ---- Loesch-Bestaetigung ---- */
document.addEventListener('submit', function (e) {
var form = e.target;
var msg = form.getAttribute && form.getAttribute('data-confirm');
if (msg && !window.confirm(msg)) {
e.preventDefault();
}
});
/* ---- Alle-auswaehlen-Checkbox (setzt alle gleichnamigen Checkboxen im Modal) ---- */
document.addEventListener('change', function (e) {
var all = e.target.closest('[data-check-all]');
if (!all) return;
var scope = all.closest('.cb-modal') || document;
scope.querySelectorAll('input[name="' + all.getAttribute('data-check-all') + '"]').forEach(function (cb) {
cb.checked = all.checked;
});
});
/* ---- Auto-Submit (Sortier-Dropdowns) ---- */
document.addEventListener('change', function (e) {
var el = e.target.closest('[data-autosubmit]');
if (el && el.form) {
el.form.submit();
}
});
/* ---- View-Toggle (Karten/Liste) ---- */
(function initViewToggle() {
var btns = document.querySelectorAll('.cb-view-btn');
if (!btns.length) return;
var cardsEl = document.getElementById('cbCardsView');
var listEl = document.getElementById('cbListView');
btns.forEach(function (btn) {
btn.addEventListener('click', function () {
var mode = btn.dataset.mode;
btns.forEach(function (b) {
b.classList.toggle('active', b === btn);
b.setAttribute('aria-pressed', b === btn ? 'true' : 'false');
});
if (cardsEl) cardsEl.classList.toggle('cb-hidden', mode !== 'cards');
if (listEl) listEl.classList.toggle('cb-hidden', mode === 'cards');
fetch('/contacts/set-view-mode', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest',
'X-CSRF-Token': getCsrf()
},
body: 'mode=' + encodeURIComponent(mode)
}).catch(function () { /* still toggled clientseitig */ });
});
});
}());
/* ---- Dynamische Kontaktkanaele ---- */
document.addEventListener('click', function (e) {
var addBtn = e.target.closest('[data-add-channel]');
if (addBtn) {
e.preventDefault();
var wrap = addBtn.closest('[data-channels]');
var tpl = wrap && wrap.querySelector('template[data-channel-template]');
var rows = wrap && wrap.querySelector('.cb-channel-rows');
if (tpl && rows) {
var idx = parseInt(addBtn.getAttribute('data-next-index') || '0', 10);
addBtn.setAttribute('data-next-index', String(idx + 1));
rows.insertAdjacentHTML('beforeend', tpl.innerHTML.replace(/__IDX__/g, String(idx)));
var added = rows.lastElementChild;
var input = added && added.querySelector('.cb-channel-value');
if (input) input.focus();
}
return;
}
var rmBtn = e.target.closest('[data-remove-channel]');
if (rmBtn) {
e.preventDefault();
var row = rmBtn.closest('.cb-channel-row');
if (row) row.remove();
}
});
}());