list: fix RecordCounter, inf scroll in search, activities

This commit is contained in:
Harvey Tindall
2025-05-26 21:52:31 +01:00
parent 4dcec4b9c7
commit d8fe593323
4 changed files with 55 additions and 53 deletions
+3 -3
View File
@@ -977,7 +977,7 @@ export class accountsList extends PaginatedList {
this._populateNumbers(); this._populateNumbers();
// FIXME: Remove! // FIXME: Remove!
(window as any).s = this; (window as any).acc = this;
let searchConfig: SearchConfiguration = { let searchConfig: SearchConfiguration = {
filterArea: this._c.filterArea, filterArea: this._c.filterArea,
@@ -988,10 +988,10 @@ export class accountsList extends PaginatedList {
filterList: document.getElementById("accounts-filter-list"), filterList: document.getElementById("accounts-filter-list"),
search: this._c.searchBox, search: this._c.searchBox,
queries: queries(), queries: queries(),
setVisibility: this.setVisibility, setVisibility: null,
clearSearchButtonSelector: ".accounts-search-clear", clearSearchButtonSelector: ".accounts-search-clear",
serverSearchButtonSelector: ".accounts-search-server", serverSearchButtonSelector: ".accounts-search-server",
onSearchCallback: (_0: number, _1: boolean, _2: boolean) => { onSearchCallback: (_0: boolean, _1: boolean) => {
this._checkCheckCount(); this._checkCheckCount();
}, },
searchServer: null, searchServer: null,
+6 -3
View File
@@ -509,13 +509,13 @@ export class activityList extends PaginatedList {
this.activities[act.id] = new Activity(act); this.activities[act.id] = new Activity(act);
ordering.push(act.id); ordering.push(act.id);
} }
this._search.setOrdering(ordering, this._c.defaultSortField, this.ascending);
}, },
updateExistingElementsFromPage: (resp: paginatedDTO) => { updateExistingElementsFromPage: (resp: paginatedDTO) => {
// FIXME: Implement updates to existing elements! // FIXME: Implement updates to existing elements, rather than just wiping each time.
for (let id of Object.keys(this.activities)) { for (let id of Object.keys(this.activities)) {
delete this.activities[id]; delete this.activities[id];
} }
this._search.setOrdering([], this._c.defaultSortField, this.ascending);
this._c.newElementsFromPage(resp); this._c.newElementsFromPage(resp);
}, },
defaultSortField: ACTIVITY_DEFAULT_SORT_FIELD, defaultSortField: ACTIVITY_DEFAULT_SORT_FIELD,
@@ -529,6 +529,9 @@ export class activityList extends PaginatedList {
} }
}); });
// FIXME: Remove!
(window as any).act = this;
this._container = document.getElementById("activity-card-list") this._container = document.getElementById("activity-card-list")
document.addEventListener("activity-reload", this.reload); document.addEventListener("activity-reload", this.reload);
@@ -543,7 +546,7 @@ export class activityList extends PaginatedList {
clearSearchButtonSelector: ".activity-search-clear", clearSearchButtonSelector: ".activity-search-clear",
serverSearchButtonSelector: ".activity-search-server", serverSearchButtonSelector: ".activity-search-server",
queries: queries(), queries: queries(),
setVisibility: this.setVisibility, setVisibility: null,
filterList: document.getElementById("activity-filter-list"), filterList: document.getElementById("activity-filter-list"),
// notFoundCallback: this._notFoundCallback, // notFoundCallback: this._notFoundCallback,
onSearchCallback: null, onSearchCallback: null,
+40 -41
View File
@@ -22,10 +22,10 @@ export class RecordCounter {
<span class="records-shown"></span> <span class="records-shown"></span>
<span class="records-selected"></span> <span class="records-selected"></span>
`; `;
this._totalRecords = document.getElementsByClassName("records-total")[0] as HTMLElement; this._totalRecords = this._container.getElementsByClassName("records-total")[0] as HTMLElement;
this._loadedRecords = document.getElementsByClassName("records-loaded")[0] as HTMLElement; this._loadedRecords = this._container.getElementsByClassName("records-loaded")[0] as HTMLElement;
this._shownRecords = document.getElementsByClassName("records-shown")[0] as HTMLElement; this._shownRecords = this._container.getElementsByClassName("records-shown")[0] as HTMLElement;
this._selectedRecords = document.getElementsByClassName("records-selected")[0] as HTMLElement; this._selectedRecords = this._container.getElementsByClassName("records-selected")[0] as HTMLElement;
this.total = 0; this.total = 0;
this.loaded = 0; this.loaded = 0;
this.shown = 0; this.shown = 0;
@@ -138,7 +138,7 @@ export abstract class PaginatedList {
} }
} }
protected _previousPageSize = 0; protected _previousVisibleItemCount = 0;
// Stores a PaginatedReqDTO-implementing thing. // Stores a PaginatedReqDTO-implementing thing.
// A standard PaginatedReqDTO will be overridden entirely, // A standard PaginatedReqDTO will be overridden entirely,
@@ -173,9 +173,7 @@ export abstract class PaginatedList {
initSearch = (searchConfig: SearchConfiguration) => { initSearch = (searchConfig: SearchConfiguration) => {
const previousCallback = searchConfig.onSearchCallback; const previousCallback = searchConfig.onSearchCallback;
searchConfig.onSearchCallback = (visibleCount: number, newItems: boolean, loadAll: boolean) => { searchConfig.onSearchCallback = (newItems: boolean, loadAll: boolean) => {
this._counter.shown = visibleCount;
// if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused"); // if (this._search.inSearch && !this.lastPage) this._c.loadAllButton.classList.remove("unfocused");
// else this._c.loadAllButton.classList.add("unfocused"); // else this._c.loadAllButton.classList.add("unfocused");
@@ -186,17 +184,17 @@ export abstract class PaginatedList {
} }
// FIXME: Figure out why this makes sense and make it clearer. // FIXME: Figure out why this makes sense and make it clearer.
if ((visibleCount < this._c.itemsPerPage && this._counter.loaded < this._c.maxItemsLoadedForSearch && !this.lastPage) || loadAll) { if ((this._visible.length < this._c.itemsPerPage && this._counter.loaded < this._c.maxItemsLoadedForSearch && !this.lastPage) || loadAll) {
if (!newItems || if (!newItems ||
this._previousPageSize != visibleCount || this._previousVisibleItemCount != this._visible.length ||
(visibleCount == 0 && !this.lastPage) || (this._visible.length == 0 && !this.lastPage) ||
loadAll loadAll
) { ) {
this.loadMore(() => {}, loadAll); this.loadMore(() => {}, loadAll);
} }
} }
this._previousPageSize = visibleCount; this._previousVisibleItemCount = this._visible.length;
if (previousCallback) previousCallback(visibleCount, newItems, loadAll); if (previousCallback) previousCallback(newItems, loadAll);
}; };
const previousServerSearch = searchConfig.searchServer; const previousServerSearch = searchConfig.searchServer;
searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean) => { searchConfig.searchServer = (params: PaginatedReqDTO, newSearch: boolean) => {
@@ -211,28 +209,28 @@ export abstract class PaginatedList {
this._page = 0; this._page = 0;
this.reload(); this.reload();
} }
searchConfig.setVisibility = this.setVisibility;
this._search = new Search(searchConfig); this._search = new Search(searchConfig);
this._search.generateFilterList(); this._search.generateFilterList();
this.lastPage = false; this.lastPage = false;
}; };
// Sets the elements with "name"s in "elements" as visible or not. // Sets the elements with "name"s in "elements" as visible or not.
setVisibilityNaive = (elements: string[], visible: boolean) => { // setVisibilityNaive = (elements: string[], visible: boolean) => {
let timer = this._search.timeSearches ? performance.now() : null; // let timer = this._search.timeSearches ? performance.now() : null;
if (visible) this._visible = elements; // if (visible) this._visible = elements;
else this._visible = this._search.ordering.filter(v => !elements.includes(v)); // else this._visible = this._search.ordering.filter(v => !elements.includes(v));
const frag = document.createDocumentFragment() // const frag = document.createDocumentFragment()
for (let i = 0; i < this._visible.length; i++) { // for (let i = 0; i < this._visible.length; i++) {
frag.appendChild(this._search.items[this._visible[i]].asElement()) // frag.appendChild(this._search.items[this._visible[i]].asElement())
} // }
this._container.replaceChildren(frag); // this._container.replaceChildren(frag);
if (this._search.timeSearches) { // if (this._search.timeSearches) {
const totalTime = performance.now() - timer; // const totalTime = performance.now() - timer;
console.log(`setVisibility took ${totalTime}ms`); // console.log(`setVisibility took ${totalTime}ms`);
} // }
} // }
// FIXME: On reload, load enough pages to fill required space.
// FIXME: Might have broken _counter.shown! // FIXME: Might have broken _counter.shown!
// Sets the elements with "name"s in "elements" as visible or not. // Sets the elements with "name"s in "elements" as visible or not.
// appendedItems==true implies "elements" is the previously rendered elements plus some new ones on the end. Knowing this means the page's infinite scroll doesn't have to be reset. // appendedItems==true implies "elements" is the previously rendered elements plus some new ones on the end. Knowing this means the page's infinite scroll doesn't have to be reset.
@@ -240,7 +238,12 @@ export abstract class PaginatedList {
let timer = this._search.timeSearches ? performance.now() : null; let timer = this._search.timeSearches ? performance.now() : null;
if (visible) this._visible = elements; if (visible) this._visible = elements;
else this._visible = this._search.ordering.filter(v => !elements.includes(v)); else this._visible = this._search.ordering.filter(v => !elements.includes(v));
if (this._visible.length == 0) return; console.log(elements.length, visible, this._visible.length);
this._counter.shown = this._visible.length;
if (this._visible.length == 0) {
this._container.textContent = ``;
return;
}
if (!appendedItems) { if (!appendedItems) {
// Wipe old elements and render 1 new one, so we can take the element height. // Wipe old elements and render 1 new one, so we can take the element height.
@@ -277,6 +280,8 @@ export abstract class PaginatedList {
// Computes required scroll info, requiring one on-DOM item. Should be computed on page resize and this._visible change. // Computes required scroll info, requiring one on-DOM item. Should be computed on page resize and this._visible change.
_computeScrollInfo = () => { _computeScrollInfo = () => {
if (this._visible.length == 0) return;
this._scroll.screenHeight = Math.max( this._scroll.screenHeight = Math.max(
document.documentElement.clientHeight, document.documentElement.clientHeight,
window.innerHeight || 0 window.innerHeight || 0
@@ -340,7 +345,7 @@ export abstract class PaginatedList {
this._counter.loaded = this._search.ordering.length; this._counter.loaded = this._search.ordering.length;
this._search.onSearchBoxChange(true); this._search.onSearchBoxChange(true, false, false);
if (this._search.inSearch) { if (this._search.inSearch) {
// this._c.loadAllButton.classList.remove("unfocused"); // this._c.loadAllButton.classList.remove("unfocused");
} else { } else {
@@ -400,7 +405,7 @@ export abstract class PaginatedList {
if (this.lastPage) { if (this.lastPage) {
loadAll = false; loadAll = false;
} }
this._search.onSearchBoxChange(true, loadAll); this._search.onSearchBoxChange(true, true, loadAll);
} else { } else {
// Since results come to us ordered already, we can assume "ordering" // Since results come to us ordered already, we can assume "ordering"
// will be identical to pre-page-load but with extra elements at the end, // will be identical to pre-page-load but with extra elements at the end,
@@ -423,15 +428,14 @@ export abstract class PaginatedList {
// As reloading can disrupt long-scrolling, this function will only do it if you're at the top of the page, essentially. // As reloading can disrupt long-scrolling, this function will only do it if you're at the top of the page, essentially.
public reloadIfNotInScroll = () => { public reloadIfNotInScroll = () => {
if (this.maximumItemsToRender(window.scrollY) < this._scroll.initialRenderCount) { if (this._visible.length == 0 || this.maximumItemsToRender(window.scrollY) < this._scroll.initialRenderCount) {
return this.reload(); return this.reload();
} }
} }
_detectScroll = () => { _detectScroll = () => {
if (!this._hasLoaded || this._scroll.scrollLoading) return; if (!this._hasLoaded || this._scroll.scrollLoading || this._visible.length == 0) return;
if (this._visible.length == 0) return;
const scrollY = window.scrollY; const scrollY = window.scrollY;
const scrollSpeed = scrollY - this._scroll.lastScrollY; const scrollSpeed = scrollY - this._scroll.lastScrollY;
this._scroll.lastScrollY = scrollY; this._scroll.lastScrollY = scrollY;
@@ -460,6 +464,7 @@ export abstract class PaginatedList {
this._scroll.scrollLoading = true; this._scroll.scrollLoading = true;
const cb = () => { const cb = () => {
if (this._visible.length < endIdx && !this.lastPage) { if (this._visible.length < endIdx && !this.lastPage) {
// FIXME: This causes scroll-to-top when in search.
this.loadMore(cb, false) this.loadMore(cb, false)
return; return;
} }
@@ -472,17 +477,11 @@ export abstract class PaginatedList {
} }
} }
// Should be assigned to window.onscroll whenever the list is in view.
detectScroll = throttle(this._detectScroll, 200); detectScroll = throttle(this._detectScroll, 200);
computeScrollInfo = throttle(this._computeScrollInfo, 200); computeScrollInfo = throttle(this._computeScrollInfo, 200);
// Should be called in window resize redrawScroll = this.computeScrollInfo;
redrawScroll = throttle(() => {
// FIXME: Make sure this is enough when rows resize, and that we don't need to re-setVisibility.
this._computeScrollInfo();
// this.setVisibility(this._visible, true, false);
}, 200);
// bindPageEvents binds window event handlers for when this list/tab containing it is visible. // bindPageEvents binds window event handlers for when this list/tab containing it is visible.
bindPageEvents = () => { bindPageEvents = () => {
+5 -5
View File
@@ -42,8 +42,8 @@ export interface SearchConfiguration {
serverSearchButtonSelector: string; serverSearchButtonSelector: string;
search: HTMLInputElement; search: HTMLInputElement;
queries: { [field: string]: QueryType }; queries: { [field: string]: QueryType };
setVisibility: (items: string[], visible: boolean) => void; setVisibility: (items: string[], visible: boolean, appendedItems: boolean) => void;
onSearchCallback: (visibleCount: number, newItems: boolean, loadAll: boolean) => void; onSearchCallback: (newItems: boolean, loadAll: boolean) => void;
searchServer: (params: PaginatedReqDTO, newSearch: boolean) => void; searchServer: (params: PaginatedReqDTO, newSearch: boolean) => void;
clearServerSearch: () => void; clearServerSearch: () => void;
loadMore?: () => void; loadMore?: () => void;
@@ -505,7 +505,7 @@ export class Search {
get sortField(): string { return this._sortField; } get sortField(): string { return this._sortField; }
get ascending(): boolean { return this._ascending; } get ascending(): boolean { return this._ascending; }
onSearchBoxChange = (newItems: boolean = false, loadAll: boolean = false) => { onSearchBoxChange = (newItems: boolean = false, appendedItems: boolean = false, loadAll: boolean = false) => {
const query = this._c.search.value; const query = this._c.search.value;
if (!query) { if (!query) {
this.inSearch = false; this.inSearch = false;
@@ -513,8 +513,8 @@ export class Search {
this.inSearch = true; this.inSearch = true;
} }
const results = this.search(query); const results = this.search(query);
this._c.setVisibility(results, true); this._c.setVisibility(results, true, appendedItems);
this._c.onSearchCallback(results.length, newItems, loadAll); this._c.onSearchCallback(newItems, loadAll);
if (this.inSearch) { if (this.inSearch) {
if (this.inServerSearch) { if (this.inServerSearch) {
this._serverSearchButtons.forEach((v: HTMLElement) => { this._serverSearchButtons.forEach((v: HTMLElement) => {