(function (app) { 'use strict'; async function init() { // Both apps (table + form) ship in the same bundle. Skip if // mode dispatcher said this isn't our mode — form-mode requests // are handled by formApp. if (window.zddcMode === 'form') { return; } const ctx = await app.modules.context.load(); app.context = ctx; const titleEl = document.getElementById('table-title'); if (ctx.title && titleEl) { titleEl.textContent = ctx.title; document.title = 'ZDDC — ' + ctx.title; } const descEl = document.getElementById('table-description'); if (descEl && ctx.description) { descEl.textContent = ctx.description; descEl.hidden = false; } const tableEl = document.getElementById('table-root'); const theadEl = tableEl.querySelector('thead'); const tbodyEl = tableEl.querySelector('tbody'); const emptyEl = document.getElementById('table-empty'); const countEl = document.getElementById('table-rowcount'); const clearBtn = document.getElementById('table-clear-filters'); const addRowBtn = document.getElementById('table-add-row'); const exportBtn = document.getElementById('table-export-csv'); // Add-row button: appends a draft row inline. Save fires on // row-blur, which POSTs to /form.html and swaps the // synthetic row id for the server's response. The button shows // whenever the page is a real table view (http(s) + a table // context loaded with columns) — the test-fixture inline-context // harness opens tables.html directly with no URL shape, so we // gate on having a column list AND running over http(s). // Export CSV: client-side build of the current view (filtered + // sorted columns + values). No server round-trip, no auth gate // — the user already has the data on screen. Shown on every // table that loaded with columns, regardless of HTTP/file://. if (exportBtn) { const hasCols = Array.isArray(ctx.columns) && ctx.columns.length > 0; if (hasCols) { exportBtn.hidden = false; exportBtn.addEventListener('click', function () { const exp = app.modules.exportCsv; if (exp && typeof exp.invoke === 'function') { exp.invoke(); } }); } } if (addRowBtn) { const onHttp = location.protocol === 'http:' || location.protocol === 'https:'; const hasCols = Array.isArray(ctx.columns) && ctx.columns.length > 0; // ctx.addable === false suppresses the affordance entirely. // Used by project-rollup tables where the row's party // affiliation is ambiguous (add at the per-party path). const allowAdd = ctx.addable !== false; if (onHttp && hasCols && allowAdd) { addRowBtn.hidden = false; addRowBtn.removeAttribute('href'); addRowBtn.setAttribute('role', 'button'); addRowBtn.setAttribute('tabindex', '0'); addRowBtn.style.cursor = 'pointer'; const handleAdd = function (ev) { ev.preventDefault(); const addRow = app.modules.addRow; if (addRow && typeof addRow.invoke === 'function') { addRow.invoke(); } }; addRowBtn.addEventListener('click', handleAdd); addRowBtn.addEventListener('keydown', function (ev) { if (ev.key === 'Enter' || ev.key === ' ') handleAdd(ev); }); } } const columns = Array.isArray(ctx.columns) ? ctx.columns : []; const allRows = Array.isArray(ctx.rows) ? ctx.rows : []; const state = app.state; state.rows = allRows; state.sort = app.modules.sort.defaultsFromContext(ctx); state.filter = {}; // Seed default filters from context.defaults.filter (per-column). if (ctx.defaults && ctx.defaults.filter && typeof ctx.defaults.filter === 'object') { for (let i = 0; i < columns.length; i++) { const col = columns[i]; const seeded = ctx.defaults.filter[col.field]; if (seeded == null) { continue; } // Filter UI is uniformly text-contains. If the spec // seeds an array (legacy enum-style), coerce to a // comma-joined contains string — partial match on any // listed value still narrows the table sensibly. const seedStr = Array.isArray(seeded) ? seeded.join(',') : String(seeded); state.filter[col.field] = { kind: 'contains', value: seedStr }; } } function anyFilterActive() { const filters = app.modules.filters; const keys = Object.keys(state.filter); for (let i = 0; i < keys.length; i++) { if (!filters.isEmpty(state.filter[keys[i]])) { return true; } } return false; } function paint() { const filtered = app.modules.filters.apply(state.rows, columns, state.filter, app.modules.util.resolveField); const sorted = app.modules.sort.apply(filtered, state.sort, columns, app.modules.util); app.modules.render.header(theadEl, columns, state.sort, state.filter, onHeaderClick, onFilterChange); app.modules.render.body(tbodyEl, sorted, columns); app.modules.render.rowCount(countEl, sorted.length, state.rows.length); if (emptyEl) { emptyEl.hidden = sorted.length > 0 || state.rows.length === 0; } if (clearBtn) { clearBtn.hidden = !anyFilterActive(); } // Restore the editor's selection across re-paints so a sort // or filter change doesn't dump the user out of the cell // they were on. Selected coords clamp to the new bounds in // setSelected; if the row vanished (filter excluded it), // we land on the last valid cell instead of clearing. const editor = app.modules.editor; if (editor) { editor.attachToTable(); if (state.selected) { editor.setSelected(state.selected.row, state.selected.col, { noFocus: true }); } } // Row context menu re-attaches each paint — renderBody wipes // the tbody, taking listeners with it. const rowOps = app.modules.rowOps; if (rowOps && typeof rowOps.attach === 'function') { rowOps.attach(); } // Re-apply Phase-3 dirty-row markers — tbody.innerHTML='' in // renderBody wiped them. const save = app.modules.save; if (save && typeof save.markAllDirtyRows === 'function') { save.markAllDirtyRows(); } } // Public re-paint entry point so other modules (save.useMine / // save.reload) can request a refresh after they mutate row state. app.repaint = paint; function onHeaderClick(field, shiftKey) { state.sort = app.modules.sort.cycle(state.sort, field, shiftKey); paint(); } function onFilterChange(field, value) { state.filter[field] = value; paint(); } if (clearBtn) { clearBtn.addEventListener('click', function () { state.filter = {}; paint(); }); } paint(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })(window.tablesApp);