/** * VITEC — collapse/expand toggle for container content elements in the * TYPO3 v14 Page module. * * Adds a chevron button into the `.btn-toolbar` of every VITEC container * (vitec_container, vitec_cols_*, vitec_cards_carousel — anything that * renders a nested b13 grid). Clicking it hides/shows the container body * (the nested child columns). State is persisted per element uid in * localStorage, so it survives reloads. * * Pure DOM enhancement — no core or b13 template overrides. */ class VitecContainerCollapse { constructor() { this.storagePrefix = 'vitec-ce-collapsed-'; this.ready(); } ready() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => this.boot()); } else { this.boot(); } } boot() { this.processAll(); // Page module can re-render columns via AJAX; watch for new elements. const root = document.querySelector('.t3js-page-columns-wrapper, .module, body'); if (root) { const observer = new MutationObserver(() => { window.clearTimeout(this._debounce); this._debounce = window.setTimeout(() => this.processAll(), 150); }); observer.observe(root, { childList: true, subtree: true }); } } processAll() { document.querySelectorAll('.t3-page-ce[data-uid]').forEach((ce) => this.enhance(ce)); } isCollapsed(uid) { try { return window.localStorage.getItem(this.storagePrefix + uid) === '1'; } catch (e) { return false; } } setCollapsed(uid, collapsed) { try { if (collapsed) { window.localStorage.setItem(this.storagePrefix + uid, '1'); } else { window.localStorage.removeItem(this.storagePrefix + uid); } } catch (e) { /* storage unavailable — ignore */ } } enhance(ce) { if (ce.dataset.vitecCollapseReady === '1') { return; } // The direct body of THIS element (not a nested child's body). const element = ce.querySelector(':scope > .t3-page-ce-element'); if (!element) { return; } const body = element.querySelector(':scope > .t3-page-ce-body'); if (!body) { return; } // A container is recognised by a nested b13 grid inside its body. const hasNestedGrid = body.querySelector('.t3-grid-container') !== null; if (!hasNestedGrid) { return; } // Best-effort VITEC filter via the header icon identifier // (b13 sets the icon identifier to the CType, e.g. "vitec_container"). const iconEl = ce.querySelector( '.t3-page-ce-header-left typo3-backend-icon[identifier], .t3-page-ce-header-left [data-identifier]' ); if (iconEl) { const identifier = iconEl.getAttribute('identifier') || iconEl.getAttribute('data-identifier') || ''; if (identifier && identifier.indexOf('vitec') === -1) { return; // a non-VITEC container — leave it alone } } const toolbar = element.querySelector( ':scope > .t3-page-ce-header .btn-toolbar .btn-group' ); if (!toolbar) { return; } ce.dataset.vitecCollapseReady = '1'; const uid = ce.dataset.uid; const button = document.createElement('button'); button.type = 'button'; button.className = 'btn btn-sm btn-default btn-borderless vitec-collapse-toggle'; button.title = 'Collapse / expand container'; button.setAttribute('aria-label', 'Collapse or expand container'); button.innerHTML = ''; toolbar.insertBefore(button, toolbar.firstChild); if (this.isCollapsed(uid)) { ce.classList.add('vitec-ce-collapsed'); } this.syncPreviewHeader(ce); button.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); const collapsed = ce.classList.toggle('vitec-ce-collapsed'); this.setCollapsed(uid, collapsed); this.syncPreviewHeader(ce); }); } /** * Keep the element's own `.element-preview-header` visible while collapsed: * it is moved into the CE header row (after `.t3-page-ce-header-right`) and * moved back to its original spot (placeholder marker) on expand. */ syncPreviewHeader(ce) { const element = ce.querySelector(':scope > .t3-page-ce-element'); if (!element) { return; } const header = element.querySelector(':scope > .t3-page-ce-header'); const body = element.querySelector(':scope > .t3-page-ce-body'); if (!header || !body) { return; } const collapsed = ce.classList.contains('vitec-ce-collapsed'); if (collapsed) { // Find THIS element's preview header (not one of a nested child CE). let preview = null; for (const candidate of body.querySelectorAll('.element-preview-header')) { if (candidate.closest('.t3-page-ce') === ce) { preview = candidate; break; } } if (!preview || header.contains(preview)) { return; } const placeholder = document.createElement('span'); placeholder.className = 'vitec-preview-header-placeholder'; placeholder.hidden = true; preview.before(placeholder); preview.classList.add('vitec-preview-header--in-header'); const headerRight = header.querySelector('.t3-page-ce-header-right'); if (headerRight) { headerRight.insertAdjacentElement('afterend', preview); } else { header.appendChild(preview); } } else { const preview = header.querySelector('.element-preview-header.vitec-preview-header--in-header'); const placeholder = body.querySelector('.vitec-preview-header-placeholder'); if (preview && placeholder) { preview.classList.remove('vitec-preview-header--in-header'); placeholder.replaceWith(preview); } else if (preview) { preview.classList.remove('vitec-preview-header--in-header'); body.prepend(preview); } } } } export default new VitecContainerCollapse();