OpenNIT

Quelltext des aktuellen Standes

Fassung 1.1.1

contacts.js

7 KB
/* ============================================
   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();
        }
    });
}());