Files
reflector-dashboard/lib/pager.js
T

94 lines
4.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Reflector Dashboard — 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: <dmr-portal>/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) =>
`<button class="btn btn-sm ${cls} ${cur ? 'btn-primary' : 'btn-outline-secondary'} mx-1"${dis ? ' disabled' : ''}${target != null ? ` data-pg="${target}"` : ''} style="padding:1px 8px;">${label}</button>`;
let h = `<div class="d-flex align-items-center flex-wrap justify-content-center mt-2" style="gap:2px;">`;
h += `<span class="small text-secondary me-2">${total} ${noun} · page ${page}/${pages}</span>`;
h += btn('&laquo;', 1, page === 1) + btn('&lsaquo;', page - 1, page === 1);
for (let p = from; p <= to; p++) h += btn(String(p), p, false, p === page);
h += btn('&rsaquo;', page + 1, page === pages) + btn('&raquo;', pages, page === pages);
return h + `</div>`;
}
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) =>
`<button class="btn btn-sm ${isCurrent ? 'btn-primary' : 'btn-outline-secondary'} mx-1"`
+ (disabled ? ' disabled' : '')
+ (target != null ? ` data-pg="${target}"` : '')
+ `>${label}</button>`;
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 =
`<div class="d-flex justify-content-center align-items-center mt-2 small">` +
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) +
`<span class="text-secondary ms-2">${cur} / ${total} (${totalItems} rows)</span>` +
`</div>`;
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;
})();