// SvxPortal — web front-end for SvxReflector // Copyright (C) 2026 SA7AUX (Björn) // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it under // the terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) any // later version. See the LICENSE file for details. // pager.js — shared pagination (dmr-web, svx-web). // Canonical copy: /lib/pager.js; other repos carry a COPY. // // pageSizeFor('monitorBody', 32, 160) rows that fit below the element (min 5) // pagerHtml({page, pages, total, noun, cls}) footer « ‹ 1..n › » as HTML; // buttons carry class `cls` + data-pg, the page wires the click. // paginate.slice / totalPages / render(container, total, pageSize, page, onChange) // the container-driven pager (dmr-web lists), 1-based pages. (function () { 'use strict'; function pageSizeFor(bodyId, rowPx, reservePx) { const body = document.getElementById(bodyId); const top = body ? body.getBoundingClientRect().top : 220; const avail = window.innerHeight - top - (reservePx == null ? 160 : reservePx); return Math.max(5, Math.floor(avail / (rowPx || 32))); } function pagerHtml(o) { const page = o.page, pages = o.pages, total = o.total, cls = o.cls || 'pg', noun = o.noun || 'rows'; if (pages <= 1) return ''; const span = 3; let from = Math.max(1, page - span), to = Math.min(pages, page + span); while (to - from < 6 && (from > 1 || to < pages)) { if (from > 1) from--; else if (to < pages) to++; } const btn = (label, target, dis, cur) => ``; let h = `
`; h += `${total} ${noun} · page ${page}/${pages}`; h += btn('«', 1, page === 1) + btn('‹', page - 1, page === 1); for (let p = from; p <= to; p++) h += btn(String(p), p, false, p === page); h += btn('›', page + 1, page === pages) + btn('»', pages, page === pages); return h + `
`; } const paginate = { slice(items, pageSize, page) { if (!Array.isArray(items)) return []; if (pageSize <= 0) return items; const start = Math.max(0, (page - 1) * pageSize); return items.slice(start, start + pageSize); }, totalPages(totalItems, pageSize) { if (pageSize <= 0 || totalItems <= 0) return 1; return Math.max(1, Math.ceil(totalItems / pageSize)); }, render(container, totalItems, pageSize, page, onChange) { if (!container) return; const total = this.totalPages(totalItems, pageSize); if (total <= 1) { container.innerHTML = ''; return; } const cur = Math.min(Math.max(1, page), total); const btn = (label, target, disabled, isCurrent) => ``; const span = 2; let from = Math.max(1, cur - span); let to = Math.min(total, cur + span); while (to - from < 4 && (from > 1 || to < total)) { if (from > 1) from--; else if (to < total) to++; } let nums = ''; for (let p = from; p <= to; p++) nums += btn(String(p), p, false, p === cur); container.innerHTML = `
` + btn('« First', 1, cur === 1, false) + btn('‹ Prev', cur - 1, cur === 1, false) + nums + btn('Next ›', cur + 1, cur === total, false) + btn('Last »', total, cur === total, false) + `${cur} / ${total} (${totalItems} rows)` + `
`; container.onclick = (e) => { const b = e.target.closest('button[data-pg]'); if (!b) return; const p = parseInt(b.dataset.pg, 10); if (!isNaN(p) && p >= 1 && p <= total && typeof onChange === 'function') { onChange(p); } }; }, }; window.pageSizeFor = pageSizeFor; window.pagerHtml = pagerHtml; window.paginate = paginate; })();