Initial implementation.
This commit is contained in:
226
pagination.js
Normal file
226
pagination.js
Normal file
@ -0,0 +1,226 @@
|
||||
/**
|
||||
@license
|
||||
Copyright (c) 2022 trading_peter
|
||||
This program is available under Apache License Version 2.0
|
||||
*/
|
||||
|
||||
import { Debouncer } from '@polymer/polymer/lib/utils/debounce.js';
|
||||
import { Helper } from '@era-core/era-mixins/era-helper-mixin.js';
|
||||
import { dedupingMixin } from '@polymer/polymer/lib/utils/mixin.js';
|
||||
import { timeOut } from '@polymer/polymer/lib/utils/async.js';
|
||||
|
||||
/**
|
||||
* # ListLoader
|
||||
*
|
||||
* Methods to load list contents page by page.
|
||||
*
|
||||
* @polymerBehavior ListLoader
|
||||
*/
|
||||
export const Pagination = dedupingMixin(function(superClass) {
|
||||
return class extends Helper(superClass) {
|
||||
static get properties() {
|
||||
return {
|
||||
_entries: { type: Array },
|
||||
_entriesSet: { type: Object },
|
||||
_page: { type: Number },
|
||||
_filter: { type: String },
|
||||
_filterPage: { type: Number },
|
||||
_limit: { type: Number }
|
||||
};
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._entries = [];
|
||||
this._entriesSet = new Set();
|
||||
this._page = 1;
|
||||
this._filterPage = 1;
|
||||
this._limit = 350;
|
||||
}
|
||||
|
||||
_fetch(query, cb) {
|
||||
// Override
|
||||
}
|
||||
|
||||
_isInFlight() {
|
||||
// Override
|
||||
}
|
||||
|
||||
get _hasFilter() {
|
||||
return typeof this._filter === 'string' && this._filter.length > 0;
|
||||
}
|
||||
|
||||
_reloadList() {
|
||||
this._resetList();
|
||||
this.__currentPage = 0;
|
||||
this._lastPage = false;
|
||||
this._listStateChanged();
|
||||
}
|
||||
|
||||
_listStateChanged() {
|
||||
if (this._hasFilter) {
|
||||
this._fetchFiltered();
|
||||
} else {
|
||||
this._fetchPage();
|
||||
}
|
||||
}
|
||||
|
||||
_scrollThresholdTriggered(e) {
|
||||
if (this.active && this._listOverflows()) {
|
||||
this.__list = e.composedPath()[0].scrollTarget;
|
||||
this.__listScrollOffset = this.__list.scrollTop;
|
||||
|
||||
if (this._hasFilter) {
|
||||
this._filterPage++;
|
||||
this._fetchFiltered();
|
||||
} else {
|
||||
this._page++;
|
||||
this._fetchPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_resetList() {
|
||||
if (typeof this.setProperties === 'function') {
|
||||
this.setProperties({ _page: 1, _filterPage: 1, _entries: [] });
|
||||
} else {
|
||||
this._page = 1;
|
||||
this._filterPage = 1;
|
||||
this._entries = [];
|
||||
}
|
||||
}
|
||||
|
||||
_shouldFetch() {
|
||||
if (this._page === undefined || this._limit === undefined) return false;
|
||||
const pageChanged = this._page !== this.__currentPage;
|
||||
|
||||
let optionsChanged = false;
|
||||
optionsChanged = JSON.stringify(this._statusFilter) !== JSON.stringify(this.__currentStatusFilter) || optionsChanged;
|
||||
optionsChanged = JSON.stringify(this._sorting) !== JSON.stringify(this.__currentSorting) || optionsChanged;
|
||||
|
||||
this.__currentStatusFilter = this._statusFilter;
|
||||
this.__currentSorting = this._sorting;
|
||||
|
||||
if (optionsChanged === true) {
|
||||
this._resetList();
|
||||
}
|
||||
|
||||
if ((pageChanged === false || this._lastPage || this._isInFlight()) && optionsChanged === false) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
_fetchPage() {
|
||||
this._filterDebouncer = Debouncer.debounce(
|
||||
this._filterDebouncer,
|
||||
timeOut.after(20),
|
||||
() => {
|
||||
if (this._shouldFetch() === false) return;
|
||||
|
||||
this._fetch({ page: this._page, limit: this._limit, options: { statusFilter: this._statusFilter, sorting: this._sorting } }, (docs, pages) => {
|
||||
if (this._page === 1) {
|
||||
this._entriesSet = new Set();
|
||||
}
|
||||
|
||||
docs = docs.filter(doc => this._entriesSet.has(doc._id) === false);
|
||||
docs.forEach(doc => this._entriesSet.add(doc._id));
|
||||
|
||||
if (this._page === 1) {
|
||||
this._entries = docs;
|
||||
} else {
|
||||
this._entries = this._entries.concat(docs);
|
||||
}
|
||||
|
||||
// Restore scrolling position in the list.
|
||||
if (this.__list !== undefined) {
|
||||
this.__list.scrollTop = this.__listScrollOffset || 0;
|
||||
}
|
||||
|
||||
this.__currentPage = this._page;
|
||||
|
||||
// Check if we reached the last page.
|
||||
this._lastPage = pages <= this._page;
|
||||
|
||||
// Clear scroll threshold so the next page can be loaded.
|
||||
this.clearTriggers();
|
||||
|
||||
this.__fillList();
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
_fetchFiltered() {
|
||||
this._filterDebouncer = Debouncer.debounce(
|
||||
this._filterDebouncer,
|
||||
timeOut.after(300),
|
||||
() => {
|
||||
if (!this._hasFilter) {
|
||||
this._lastPage = false;
|
||||
this.__currentPage = false;
|
||||
this._lastFilter = null;
|
||||
this._page = 1;
|
||||
this._filterPage = 1;
|
||||
this.__listScrollOffset = 0;
|
||||
this._fetchPage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._shouldFetch() === false && this._lastPage === true && this._filter === this._lastFilter) return;
|
||||
|
||||
if (this._filter !== this._lastFilter) {
|
||||
this._filterPage = 1;
|
||||
}
|
||||
|
||||
this._lastFilter = this._filter;
|
||||
|
||||
this._fetch({ filter: this._filter, page: this._filterPage, limit: this._limit, options: {
|
||||
statusFilter: this._statusFilter,
|
||||
sorting: this._sorting
|
||||
} }, (result, resp) => {
|
||||
if (resp && resp.statusCode === 200) {
|
||||
if (this._filterPage === 1) {
|
||||
this._entries = result.entries.map(entry => entry.doc);
|
||||
} else {
|
||||
this._entries = this._entries.concat(result.entries.map(entry => entry.doc));
|
||||
}
|
||||
|
||||
// Restore scrolling position in the list.
|
||||
if (this.__list !== undefined) {
|
||||
this.__list.scrollTop = this.__listScrollOffset || 0;
|
||||
}
|
||||
|
||||
// Check if we reached the last page.
|
||||
this._lastPage = result.pages <= this._filterPage || result.pages === 0;
|
||||
|
||||
// Clear scroll threshold so the next page can be loaded.
|
||||
this.clearTriggers();
|
||||
|
||||
this.__fillList();
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Load pages until the list fills the screen.
|
||||
// We check the list height after next render to see if the list already fills the screen.
|
||||
// If not, load the next page.
|
||||
// This needs to be triggered async so that the current `_fetchPage` request
|
||||
// is finished and no longer considered "in flight".
|
||||
__fillList() {
|
||||
if (!this._lastPage && !this.listOverflows()) {
|
||||
setTimeout(() => {
|
||||
if (this._hasFilter) {
|
||||
this._filterPage++;
|
||||
this._fetchFiltered();
|
||||
} else {
|
||||
this._page++;
|
||||
this._fetchPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
Reference in New Issue
Block a user