client/paging: improve endless scroll
This commit is contained in:
parent
76882b59ef
commit
ee829e42d2
1 changed files with 102 additions and 98 deletions
|
@ -18,81 +18,31 @@ class EndlessPageView {
|
||||||
const target = document.getElementById('content-holder');
|
const target = document.getElementById('content-holder');
|
||||||
const source = this._holderTemplate();
|
const source = this._holderTemplate();
|
||||||
const pageHeaderHolder = source.querySelector('.page-header-holder');
|
const pageHeaderHolder = source.querySelector('.page-header-holder');
|
||||||
const pagesHolder = source.querySelector('.pages-holder');
|
this._pagesHolder = source.querySelector('.pages-holder');
|
||||||
views.listenToMessages(source);
|
views.listenToMessages(source);
|
||||||
views.showView(target, source);
|
views.showView(target, source);
|
||||||
this._active = true;
|
this._active = true;
|
||||||
this._working = 0;
|
this._working = 0;
|
||||||
|
this._init = true;
|
||||||
|
|
||||||
ctx.headerContext.target = pageHeaderHolder;
|
ctx.headerContext.target = pageHeaderHolder;
|
||||||
if (ctx.headerRenderer) {
|
if (ctx.headerRenderer) {
|
||||||
ctx.headerRenderer.render(ctx.headerContext);
|
ctx.headerRenderer.render(ctx.headerContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
const threshold = window.innerHeight / 3;
|
this.threshold = window.innerHeight / 3;
|
||||||
|
|
||||||
this.minPageShown = null;
|
this.minPageShown = null;
|
||||||
this.maxPageShown = null;
|
this.maxPageShown = null;
|
||||||
this.totalPages = null;
|
this.totalPages = null;
|
||||||
this.currentPage = null;
|
this.currentPage = null;
|
||||||
|
|
||||||
this._updater = () => {
|
this._loadPage(ctx, ctx.searchQuery.page, true);
|
||||||
if (this._working) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let topPageNode = null;
|
|
||||||
let element = document.elementFromPoint(window.innerWidth / 2, 1);
|
|
||||||
while (element.parentNode !== null) {
|
|
||||||
if (element.classList.contains('page')) {
|
|
||||||
topPageNode = element;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
element = element.parentNode;
|
|
||||||
}
|
|
||||||
if (!topPageNode) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let topPageNumber = parseInt(topPageNode.getAttribute('data-page'));
|
|
||||||
if (topPageNumber !== this.currentPage) {
|
|
||||||
router.replace(
|
|
||||||
_formatUrl(ctx.clientUrl, topPageNumber),
|
|
||||||
{},
|
|
||||||
false);
|
|
||||||
this.currentPage = topPageNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.totalPages === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let scrollHeight =
|
|
||||||
document.documentElement.scrollHeight -
|
|
||||||
document.documentElement.clientHeight;
|
|
||||||
|
|
||||||
if (this.minPageShown > 1 && window.scrollY - threshold < 0) {
|
|
||||||
this._loadPage(pagesHolder, ctx, this.minPageShown - 1, false)
|
|
||||||
.then(() => this._updater());
|
|
||||||
} else if (this.maxPageShown < this.totalPages &&
|
|
||||||
window.scrollY + threshold > scrollHeight) {
|
|
||||||
this._loadPage(pagesHolder, ctx, this.maxPageShown + 1, true)
|
|
||||||
.then(() => this._updater());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
this._loadPage(pagesHolder, ctx, ctx.searchQuery.page, true)
|
|
||||||
.then(pageNode => {
|
|
||||||
if (ctx.searchQuery.page > 1) {
|
|
||||||
window.scroll(0, pageNode.getBoundingClientRect().top);
|
|
||||||
}
|
|
||||||
this._updater();
|
|
||||||
});
|
|
||||||
window.addEventListener('scroll', this._updater, true);
|
|
||||||
window.addEventListener('unload', this._scrollToTop, true);
|
window.addEventListener('unload', this._scrollToTop, true);
|
||||||
|
this._probePageLoad(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
unrender() {
|
unrender() {
|
||||||
this._active = false;
|
this._active = false;
|
||||||
window.removeEventListener('scroll', this._updater, true);
|
|
||||||
window.removeEventListener('unload', this._scrollToTop, true);
|
window.removeEventListener('unload', this._scrollToTop, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,61 +50,115 @@ class EndlessPageView {
|
||||||
window.scroll(0, 0);
|
window.scroll(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
_loadPage(pagesHolder, ctx, pageNumber, append) {
|
_probePageLoad(ctx) {
|
||||||
|
if (this._active) {
|
||||||
|
window.setTimeout(() => {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
this._probePageLoad(ctx);
|
||||||
|
});
|
||||||
|
}, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._working) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let topPageNode = null;
|
||||||
|
let element = document.elementFromPoint(
|
||||||
|
window.innerWidth / 2,
|
||||||
|
window.innerHeight / 2);
|
||||||
|
while (element.parentNode !== null) {
|
||||||
|
if (element.classList.contains('page')) {
|
||||||
|
topPageNode = element;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
element = element.parentNode;
|
||||||
|
}
|
||||||
|
if (!topPageNode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let topPageNumber = parseInt(topPageNode.getAttribute('data-page'));
|
||||||
|
if (topPageNumber !== this.currentPage) {
|
||||||
|
router.replace(
|
||||||
|
_formatUrl(ctx.clientUrl, topPageNumber),
|
||||||
|
{},
|
||||||
|
false);
|
||||||
|
this.currentPage = topPageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.totalPages === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let scrollHeight =
|
||||||
|
document.documentElement.scrollHeight -
|
||||||
|
document.documentElement.clientHeight;
|
||||||
|
|
||||||
|
if (this.minPageShown > 1 && window.scrollY < this.threshold) {
|
||||||
|
this._loadPage(ctx, this.minPageShown - 1, false);
|
||||||
|
} else if (this.maxPageShown < this.totalPages &&
|
||||||
|
window.scrollY + this.threshold > scrollHeight) {
|
||||||
|
this._loadPage(ctx, this.maxPageShown + 1, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_loadPage(ctx, pageNumber, append) {
|
||||||
this._working++;
|
this._working++;
|
||||||
return ctx.requestPage(pageNumber).then(response => {
|
ctx.requestPage(pageNumber).then(response => {
|
||||||
if (!this._active) {
|
if (!this._active) {
|
||||||
this._working--;
|
this._working--;
|
||||||
return Promise.reject();
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
this.totalPages = Math.ceil(response.total / response.pageSize);
|
this.totalPages = Math.ceil(response.total / response.pageSize);
|
||||||
if (response.total) {
|
window.requestAnimationFrame(() => {
|
||||||
const pageNode = this._pageTemplate({
|
this._renderPage(
|
||||||
page: pageNumber,
|
ctx, pageNumber, append, response);
|
||||||
totalPages: this.totalPages,
|
|
||||||
});
|
|
||||||
pageNode.setAttribute('data-page', pageNumber);
|
|
||||||
|
|
||||||
Object.assign(ctx.pageContext, response);
|
|
||||||
ctx.pageContext.target = pageNode.querySelector(
|
|
||||||
'.page-content-holder');
|
|
||||||
ctx.pageRenderer.render(ctx.pageContext);
|
|
||||||
|
|
||||||
if (pageNumber < this.minPageShown ||
|
|
||||||
this.minPageShown === null) {
|
|
||||||
this.minPageShown = pageNumber;
|
|
||||||
}
|
|
||||||
if (pageNumber > this.maxPageShown ||
|
|
||||||
this.maxPageShown === null) {
|
|
||||||
this.maxPageShown = pageNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (append) {
|
|
||||||
pagesHolder.appendChild(pageNode);
|
|
||||||
} else {
|
|
||||||
pagesHolder.prependChild(pageNode);
|
|
||||||
|
|
||||||
// note: with probability of 75%, if the user has scrolled
|
|
||||||
// with a mouse wheel, chrome 49 doesn't give a slightest
|
|
||||||
// fuck about this and loads all the way to page 1 at once
|
|
||||||
window.scroll(
|
|
||||||
window.scrollX,
|
|
||||||
window.scrollY + pageNode.offsetHeight);
|
|
||||||
}
|
|
||||||
this._working--;
|
this._working--;
|
||||||
return Promise.resolve(pageNode);
|
});
|
||||||
}
|
|
||||||
if (response.total <= (pageNumber - 1) * response.pageSize) {
|
|
||||||
events.notify(events.Info, 'No data to show');
|
|
||||||
}
|
|
||||||
this._working--;
|
|
||||||
return Promise.reject();
|
|
||||||
}, response => {
|
}, response => {
|
||||||
events.notify(events.Error, response.description);
|
events.notify(events.Error, response.description);
|
||||||
this._working--;
|
this._working--;
|
||||||
return Promise.reject();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_renderPage(ctx, pageNumber, append, response) {
|
||||||
|
if (response.total) {
|
||||||
|
const pageNode = this._pageTemplate({
|
||||||
|
page: pageNumber,
|
||||||
|
totalPages: this.totalPages,
|
||||||
|
});
|
||||||
|
pageNode.setAttribute('data-page', pageNumber);
|
||||||
|
|
||||||
|
Object.assign(ctx.pageContext, response);
|
||||||
|
ctx.pageContext.target = pageNode.querySelector(
|
||||||
|
'.page-content-holder');
|
||||||
|
ctx.pageRenderer.render(ctx.pageContext);
|
||||||
|
|
||||||
|
if (pageNumber < this.minPageShown ||
|
||||||
|
this.minPageShown === null) {
|
||||||
|
this.minPageShown = pageNumber;
|
||||||
|
}
|
||||||
|
if (pageNumber > this.maxPageShown ||
|
||||||
|
this.maxPageShown === null) {
|
||||||
|
this.maxPageShown = pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
this._pagesHolder.appendChild(pageNode);
|
||||||
|
/*if (this._init && pageNumber !== 1) {
|
||||||
|
window.scroll(0, pageNode.getBoundingClientRect().top);
|
||||||
|
}*/
|
||||||
|
} else {
|
||||||
|
this._pagesHolder.prependChild(pageNode);
|
||||||
|
|
||||||
|
window.scroll(
|
||||||
|
window.scrollX,
|
||||||
|
window.scrollY + pageNode.offsetHeight);
|
||||||
|
}
|
||||||
|
} else if (response.total <= (pageNumber - 1) * response.pageSize) {
|
||||||
|
events.notify(events.Info, 'No data to show');
|
||||||
|
}
|
||||||
|
this._init = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = EndlessPageView;
|
module.exports = EndlessPageView;
|
||||||
|
|
Loading…
Reference in a new issue