112 lines
3.6 KiB
JavaScript
112 lines
3.6 KiB
JavaScript
function Table(cfg) {
|
|
return {
|
|
id: cfg.id,
|
|
refreshUrl: cfg.refreshUrl,
|
|
headers: cfg.headers || [],
|
|
fields: cfg.fields || [],
|
|
// external API
|
|
perPage: cfg.perPage || 10,
|
|
offset: cfg.offset || 0,
|
|
|
|
// derived + server-fed state
|
|
page: Math.floor((cfg.offset || 0) / (cfg.perPage || 10)) + 1,
|
|
total: 0,
|
|
pages: 0,
|
|
|
|
init() {
|
|
if (this.refreshUrl) this.refresh();
|
|
},
|
|
|
|
buildRefreshUrl() {
|
|
if (!this.refreshUrl) return null;
|
|
const u = new URL(this.refreshUrl, window.location.origin);
|
|
|
|
// We want server-side pagination with page/per_page
|
|
u.searchParams.set('page', this.page);
|
|
u.searchParams.set('per_page', this.perPage);
|
|
|
|
// Send requested fields in the way your backend expects
|
|
// If your route only supports "fields=a,b,c", then use:
|
|
if (this.fields.length) u.searchParams.set('fields_csv', this.fields.join(','));
|
|
|
|
return u.toString();
|
|
},
|
|
|
|
async refresh() {
|
|
const url = this.buildRefreshUrl();
|
|
if (!url) return;
|
|
const res = await fetch(url, { headers: { 'X-Requested-With': 'fetch' } });
|
|
const html = await res.text();
|
|
|
|
// Dump the server-rendered <tr> rows into the tbody
|
|
if (this.$refs.body) this.$refs.body.innerHTML = html;
|
|
|
|
// Read pagination metadata from headers
|
|
const toInt = (v, d=0) => {
|
|
const n = parseInt(v ?? '', 10);
|
|
return Number.isFinite(n) ? n : d;
|
|
};
|
|
|
|
const total = toInt(res.headers.get('X-Total'));
|
|
const pages = toInt(res.headers.get('X-Pages'));
|
|
const page = toInt(res.headers.get('X-Page'), this.page);
|
|
const per = toInt(res.headers.get('X-Per-Page'), this.perPage);
|
|
|
|
// Update local state
|
|
this.total = total;
|
|
this.pages = pages;
|
|
this.page = page;
|
|
this.perPage = per;
|
|
this.offset = (this.page - 1) * this.perPage;
|
|
|
|
// Update pager UI (if you put <ul x-ref="pagination"> in your caption)
|
|
this.buildPager();
|
|
// Caption numbers are bound via x-text so they auto-update.
|
|
},
|
|
|
|
buildPager() {
|
|
const ul = this.$refs.pagination;
|
|
if (!ul) return;
|
|
ul.innerHTML = '';
|
|
|
|
const mk = (label, page, disabled=false, active=false) => {
|
|
const li = document.createElement('li');
|
|
li.className = `page-item${disabled ? ' disabled' : ''}${active ? ' active' : ''}`;
|
|
const a = document.createElement('a');
|
|
a.className = 'page-link';
|
|
a.href = '#';
|
|
a.textContent = label;
|
|
a.onclick = (e) => {
|
|
e.preventDefault();
|
|
if (disabled || active) return;
|
|
this.page = page;
|
|
this.refresh();
|
|
};
|
|
li.appendChild(a);
|
|
return li;
|
|
};
|
|
|
|
// Prev
|
|
ul.appendChild(mk('«', Math.max(1, this.page - 1), this.page <= 1));
|
|
|
|
// Windowed page buttons
|
|
const maxButtons = 7;
|
|
let start = Math.max(1, this.page - Math.floor(maxButtons/2));
|
|
let end = Math.min(this.pages || 1, start + maxButtons - 1);
|
|
start = Math.max(1, Math.min(start, Math.max(1, end - maxButtons + 1)));
|
|
|
|
if (start > 1) ul.appendChild(mk('1', 1));
|
|
if (start > 2) ul.appendChild(mk('…', this.page, true));
|
|
|
|
for (let p = start; p <= end; p++) {
|
|
ul.appendChild(mk(String(p), p, false, p === this.page));
|
|
}
|
|
|
|
if (end < (this.pages || 1) - 1) ul.appendChild(mk('…', this.page, true));
|
|
if (end < (this.pages || 1)) ul.appendChild(mk(String(this.pages), this.pages));
|
|
|
|
// Next
|
|
ul.appendChild(mk('»', Math.min(this.pages || 1, this.page + 1), this.page >= (this.pages || 1)));
|
|
},
|
|
};
|
|
}
|