jf_activity: paginated list in ui

added POST route for pagination to activity route, a count route, and
modified Search and PaginatedList a bit to support lists without search
fields (essentially just running an empty search). Visible by clicking
on a user's name in the accounts tab.
This commit is contained in:
Harvey Tindall
2025-12-20 18:27:39 +00:00
parent d72a5c91cf
commit b6459b665d
16 changed files with 738 additions and 193 deletions
+42 -32
View File
@@ -18,18 +18,20 @@ export class RecordCounter {
private _loaded: number;
private _shown: number;
private _selected: number;
constructor(container: HTMLElement) {
this._container = container;
this._container.innerHTML = `
constructor(container?: HTMLElement) {
if (container) {
this._container = container;
this._container.innerHTML = `
<span class="records-total"></span>
<span class="records-loaded"></span>
<span class="records-shown"></span>
<span class="records-selected"></span>
`;
this._totalRecords = this._container.getElementsByClassName("records-total")[0] as HTMLElement;
this._loadedRecords = this._container.getElementsByClassName("records-loaded")[0] as HTMLElement;
this._shownRecords = this._container.getElementsByClassName("records-shown")[0] as HTMLElement;
this._selectedRecords = this._container.getElementsByClassName("records-selected")[0] as HTMLElement;
this._totalRecords = this._container.getElementsByClassName("records-total")[0] as HTMLElement;
this._loadedRecords = this._container.getElementsByClassName("records-loaded")[0] as HTMLElement;
this._shownRecords = this._container.getElementsByClassName("records-shown")[0] as HTMLElement;
this._selectedRecords = this._container.getElementsByClassName("records-selected")[0] as HTMLElement;
}
this.total = 0;
this.loaded = 0;
this.shown = 0;
@@ -55,7 +57,7 @@ export class RecordCounter {
}
set total(v: number) {
this._total = v;
this._totalRecords.textContent = window.lang.var("strings", "totalRecords", `${v}`);
if (this._totalRecords) this._totalRecords.textContent = window.lang.var("strings", "totalRecords", `${v}`);
}
get loaded(): number {
@@ -63,7 +65,7 @@ export class RecordCounter {
}
set loaded(v: number) {
this._loaded = v;
this._loadedRecords.textContent = window.lang.var("strings", "loadedRecords", `${v}`);
if (this._loadedRecords) this._loadedRecords.textContent = window.lang.var("strings", "loadedRecords", `${v}`);
}
get shown(): number {
@@ -71,7 +73,7 @@ export class RecordCounter {
}
set shown(v: number) {
this._shown = v;
this._shownRecords.textContent = window.lang.var("strings", "shownRecords", `${v}`);
if (this._shownRecords) this._shownRecords.textContent = window.lang.var("strings", "shownRecords", `${v}`);
}
get selected(): number {
@@ -79,8 +81,10 @@ export class RecordCounter {
}
set selected(v: number) {
this._selected = v;
if (v == 0) this._selectedRecords.textContent = ``;
else this._selectedRecords.textContent = window.lang.var("strings", "selectedRecords", `${v}`);
if (this._selectedRecords) {
if (v == 0) this._selectedRecords.textContent = ``;
else this._selectedRecords.textContent = window.lang.var("strings", "selectedRecords", `${v}`);
}
}
}
@@ -88,20 +92,21 @@ export interface PaginatedListConfig {
loader: HTMLElement;
loadMoreButtons: Array<HTMLButtonElement>;
loadAllButtons: Array<HTMLButtonElement>;
refreshButton: HTMLButtonElement;
filterArea: HTMLElement;
searchOptionsHeader: HTMLElement;
searchBox: HTMLInputElement;
recordCounter: HTMLElement;
totalEndpoint: string;
getPageEndpoint: string;
refreshButton?: HTMLButtonElement;
filterArea?: HTMLElement;
searchOptionsHeader?: HTMLElement;
searchBox?: HTMLInputElement;
recordCounter?: HTMLElement;
totalEndpoint: string | (() => string);
getPageEndpoint: string | (() => string);
itemsPerPage: number;
maxItemsLoadedForSearch: number;
appendNewItems: (resp: paginatedDTO) => void;
replaceWithNewItems: (resp: paginatedDTO) => void;
defaultSortField: string;
defaultSortAscending: boolean;
defaultSortField?: string;
defaultSortAscending?: boolean;
pageLoadCallback?: (req: XMLHttpRequest) => void;
disableSearch?: boolean;
}
export abstract class PaginatedList {
@@ -186,10 +191,11 @@ export abstract class PaginatedList {
this.loadMore(() => removeLoader(this._keepSearchingButton, true));
}; */
// Since this.reload doesn't exist, we need an arrow function to wrap it.
this._c.refreshButton.onclick = () => this.reload();
if (this._c.refreshButton) this._c.refreshButton.onclick = () => this.reload();
}
autoSetServerSearchButtonsDisabled = () => {
if (!this._search) return;
const serverSearchSortChanged =
this._search.inServerSearch &&
(this._searchParams.sortByField != this._search.sortField ||
@@ -259,7 +265,7 @@ export abstract class PaginatedList {
};
searchConfig.setVisibility = this.setVisibility;
this._search = new Search(searchConfig);
this._search.generateFilterList();
if (!this._c.disableSearch) this._search.generateFilterList();
this.lastPage = false;
};
@@ -270,7 +276,7 @@ export abstract class PaginatedList {
// else this._visible = this._search.ordering.filter(v => !elements.includes(v));
// const frag = document.createDocumentFragment()
// for (let i = 0; i < this._visible.length; i++) {
// frag.appendChild(this._search.items[this._visible[i]].asElement())
// frag.appendChild(this._search.items.get(this._visible[i]).asElement())
// }
// this._container.replaceChildren(frag);
// if (this._search.timeSearches) {
@@ -295,7 +301,7 @@ export abstract class PaginatedList {
if (!appendedItems) {
// Wipe old elements and render 1 new one, so we can take the element height.
this._container.replaceChildren(this._search.items[this._visible[0]].asElement());
this._container.replaceChildren(this._search.items.get(this._visible[0]).asElement());
}
this._computeScrollInfo();
@@ -317,7 +323,7 @@ export abstract class PaginatedList {
}
const frag = document.createDocumentFragment();
for (let i = baseIndex; i < this._scroll.initialRenderCount; i++) {
frag.appendChild(this._search.items[this._visible[i]].asElement());
frag.appendChild(this._search.items.get(this._visible[i]).asElement());
}
this._scroll.rendered = Math.max(baseIndex, this._scroll.initialRenderCount);
// appendChild over replaceChildren because there's already elements on the DOM
@@ -335,7 +341,7 @@ export abstract class PaginatedList {
this._scroll.screenHeight = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
this._scroll.rowHeight = this._search.items[this._visible[0]].asElement().offsetHeight;
this._scroll.rowHeight = this._search.items.get(this._visible[0]).asElement().offsetHeight;
};
// returns the item index to render up to for the given scroll position.
@@ -364,7 +370,7 @@ export abstract class PaginatedList {
}
_post(
this._c.getPageEndpoint,
typeof this._c.getPageEndpoint === "string" ? this._c.getPageEndpoint : this._c.getPageEndpoint(),
params,
(req: XMLHttpRequest) => {
if (req.readyState != 4) return;
@@ -397,7 +403,9 @@ export abstract class PaginatedList {
protected _reload = (callback?: (resp: paginatedDTO) => void) => {
this.lastPage = false;
this._counter.reset();
this._counter.getTotal(this._c.totalEndpoint);
this._counter.getTotal(
typeof this._c.totalEndpoint === "string" ? this._c.totalEndpoint : this._c.totalEndpoint(),
);
// Reload all currently visible elements, i.e. Load a new page of size (limit*(page+1)).
let limit = this._c.itemsPerPage;
if (this._page != 0) {
@@ -409,8 +417,10 @@ export abstract class PaginatedList {
this._c.replaceWithNewItems,
(_0: paginatedDTO) => {
// Allow refreshes every 15s
this._c.refreshButton.disabled = true;
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
if (this._c.refreshButton) {
this._c.refreshButton.disabled = true;
setTimeout(() => (this._c.refreshButton.disabled = false), 15000);
}
},
(resp: paginatedDTO) => {
this._search.onSearchBoxChange(true, false, false);
@@ -507,7 +517,7 @@ export abstract class PaginatedList {
const realEndIdx = Math.min(endIdx, this._visible.length);
const frag = document.createDocumentFragment();
for (let i = this._scroll.rendered; i < realEndIdx; i++) {
frag.appendChild(this._search.items[this._visible[i]].asElement());
frag.appendChild(this._search.items.get(this._visible[i]).asElement());
}
this._scroll.rendered = realEndIdx;
this._container.appendChild(frag);