client/paging: improve endless scroll

This commit is contained in:
rr- 2016-06-12 21:01:18 +02:00
parent 76882b59ef
commit ee829e42d2

View file

@ -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;