client/paging: improve endless paging
- Change page number indicator - Remove most of the scrolling cruft as it has no chance of working anyway, thanks to our benevolent browsers - Scroll to page header if the page is not the first one (I'm surprised this even works) - Use promises rather than timers - Simplify top page detection using .elementFromPoint rather than iterating over all pages. Coincidentally, this seems to work slightly better
This commit is contained in:
parent
f39e58b1bc
commit
29b0fde9a7
3 changed files with 82 additions and 68 deletions
|
@ -166,9 +166,20 @@ nav.text-nav
|
||||||
.page
|
.page
|
||||||
position: relative
|
position: relative
|
||||||
p
|
p
|
||||||
position: absolute
|
margin: 0.5em 0.5em 0.5em 0
|
||||||
right: 0
|
position: relative
|
||||||
top: 0
|
&:before
|
||||||
margin: 0
|
display: block
|
||||||
padding: 0.1em 0.5em
|
content: ''
|
||||||
background: white
|
position: absolute
|
||||||
|
left: 0
|
||||||
|
top: 50%
|
||||||
|
right: 0
|
||||||
|
height: 3px
|
||||||
|
background: $top-nav-color
|
||||||
|
z-index: 1
|
||||||
|
span
|
||||||
|
position: relative
|
||||||
|
background: white
|
||||||
|
padding: 0 1em
|
||||||
|
z-index: 2
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
<div class='page'>
|
<div class='page'>
|
||||||
<p>{{this.page}}/{{this.totalPages}}</p>
|
<p><span>Page {{this.page}} of {{this.totalPages}}</span></p>
|
||||||
<div class='page-content-holder'></div>
|
<div class='page-content-holder'></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -18,6 +18,7 @@ class EndlessPageView {
|
||||||
views.listenToMessages(target);
|
views.listenToMessages(target);
|
||||||
views.showView(target, source);
|
views.showView(target, source);
|
||||||
this.active = true;
|
this.active = true;
|
||||||
|
this.working = 0;
|
||||||
|
|
||||||
let headerRendererCtx = ctx;
|
let headerRendererCtx = ctx;
|
||||||
headerRendererCtx.target = pageHeaderHolder;
|
headerRendererCtx.target = pageHeaderHolder;
|
||||||
|
@ -25,47 +26,39 @@ class EndlessPageView {
|
||||||
|
|
||||||
const threshold = window.innerHeight / 3;
|
const threshold = window.innerHeight / 3;
|
||||||
|
|
||||||
if (ctx.state && ctx.state.html) {
|
this.minPageShown = null;
|
||||||
this.minPageShown = ctx.state.minPageShown;
|
this.maxPageShown = null;
|
||||||
this.maxPageShown = ctx.state.maxPageShown;
|
this.totalPages = null;
|
||||||
this.totalPages = ctx.state.totalPages;
|
this.currentPage = null;
|
||||||
this.currentPage = ctx.state.currentPage;
|
|
||||||
} else {
|
|
||||||
this.minPageShown = null;
|
|
||||||
this.maxPageShown = null;
|
|
||||||
this.totalPages = null;
|
|
||||||
this.currentPage = null;
|
|
||||||
}
|
|
||||||
this.fetching = false;
|
|
||||||
|
|
||||||
this.updater = () => {
|
this.updater = () => {
|
||||||
let topPage = null;
|
if (this.working) {
|
||||||
let allPageNodes =
|
return;
|
||||||
pagesHolder.querySelectorAll('.page');
|
|
||||||
for (let pageNode of allPageNodes) {
|
|
||||||
if (pageNode.getBoundingClientRect().bottom >= 0) {
|
|
||||||
topPage = parseInt(pageNode.getAttribute('data-page'));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (topPage !== this.currentPage) {
|
|
||||||
page.replace(
|
|
||||||
ctx.clientUrl.format({page: topPage}),
|
|
||||||
{
|
|
||||||
minPageShown: this.minPageShown,
|
|
||||||
maxPageShown: this.maxPageShown,
|
|
||||||
totalPages: this.totalPages,
|
|
||||||
currentPage: this.currentPage,
|
|
||||||
html: pagesHolder.innerHTML,
|
|
||||||
scrollX: window.scrollX,
|
|
||||||
scrollY: window.scrollY,
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
false);
|
|
||||||
this.currentPage = topPage;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.fetching || this.totalPages === null) {
|
let topPageNode = null;
|
||||||
|
var 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) {
|
||||||
|
page.replace(
|
||||||
|
ctx.clientUrl.format({page: topPageNumber}),
|
||||||
|
null,
|
||||||
|
false,
|
||||||
|
false);
|
||||||
|
this.currentPage = topPageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.totalPages === null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let scrollHeight =
|
let scrollHeight =
|
||||||
|
@ -73,41 +66,42 @@ class EndlessPageView {
|
||||||
document.documentElement.clientHeight;
|
document.documentElement.clientHeight;
|
||||||
|
|
||||||
if (this.minPageShown > 1 && window.scrollY - threshold < 0) {
|
if (this.minPageShown > 1 && window.scrollY - threshold < 0) {
|
||||||
this.loadPage(pagesHolder, ctx, this.minPageShown - 1, false);
|
this.loadPage(pagesHolder, ctx, this.minPageShown - 1, false)
|
||||||
|
.then(() => this.updater());
|
||||||
} else if (this.maxPageShown < this.totalPages &&
|
} else if (this.maxPageShown < this.totalPages &&
|
||||||
window.scrollY + threshold > scrollHeight) {
|
window.scrollY + threshold > scrollHeight) {
|
||||||
this.loadPage(pagesHolder, ctx, this.maxPageShown + 1, true);
|
this.loadPage(pagesHolder, ctx, this.maxPageShown + 1, true)
|
||||||
|
.then(() => this.updater());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ctx.state && ctx.state.html) {
|
this.loadPage(pagesHolder, ctx, ctx.searchQuery.page, true)
|
||||||
pagesHolder.innerHTML = ctx.state.html;
|
.then(pageNode => {
|
||||||
window.scroll(ctx.state.scrollX, ctx.state.scrollY);
|
if (ctx.searchQuery.page > 1) {
|
||||||
this.updater();
|
window.scroll(0, pageNode.getBoundingClientRect().top);
|
||||||
} else {
|
}
|
||||||
this.loadPage(pagesHolder, ctx, ctx.searchQuery.page, true);
|
this.updater();
|
||||||
}
|
});
|
||||||
window.addEventListener('scroll', this.updater, true);
|
window.addEventListener('scroll', this.updater, true);
|
||||||
|
window.addEventListener('unload', this.scrollToTop, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
unrender() {
|
unrender() {
|
||||||
this.active = false;
|
this.active = false;
|
||||||
window.removeEventListener('scroll', this.updater, true);
|
window.removeEventListener('scroll', this.updater, true);
|
||||||
|
window.removeEventListener('unload', this.scrollToTop, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
scrollToTop() {
|
||||||
|
window.scroll(0, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
loadPage(pagesHolder, ctx, pageNumber, append) {
|
loadPage(pagesHolder, ctx, pageNumber, append) {
|
||||||
this.fetching = true;
|
this.working++;
|
||||||
|
return ctx.requestPage(pageNumber).then(response => {
|
||||||
if (pageNumber < this.minPageShown || this.minPageShown === null) {
|
|
||||||
this.minPageShown = pageNumber;
|
|
||||||
}
|
|
||||||
if (pageNumber > this.maxPageShown || this.maxPageShown === null) {
|
|
||||||
this.maxPageShown = pageNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.requestPage(pageNumber).then(response => {
|
|
||||||
if (!this.active) {
|
if (!this.active) {
|
||||||
return;
|
this.working--;
|
||||||
|
return Promise.reject();
|
||||||
}
|
}
|
||||||
this.totalPages = Math.ceil(response.total / response.pageSize);
|
this.totalPages = Math.ceil(response.total / response.pageSize);
|
||||||
if (response.total) {
|
if (response.total) {
|
||||||
|
@ -122,6 +116,13 @@ class EndlessPageView {
|
||||||
'.page-content-holder');
|
'.page-content-holder');
|
||||||
ctx.pageRenderer.render(pageRendererCtx);
|
ctx.pageRenderer.render(pageRendererCtx);
|
||||||
|
|
||||||
|
if (pageNumber < this.minPageShown || this.minPageShown === null) {
|
||||||
|
this.minPageShown = pageNumber;
|
||||||
|
}
|
||||||
|
if (pageNumber > this.maxPageShown || this.maxPageShown === null) {
|
||||||
|
this.maxPageShown = pageNumber;
|
||||||
|
}
|
||||||
|
|
||||||
if (append) {
|
if (append) {
|
||||||
pagesHolder.appendChild(pageNode);
|
pagesHolder.appendChild(pageNode);
|
||||||
} else {
|
} else {
|
||||||
|
@ -134,16 +135,18 @@ class EndlessPageView {
|
||||||
window.scrollX,
|
window.scrollX,
|
||||||
window.scrollY + pageNode.offsetHeight);
|
window.scrollY + pageNode.offsetHeight);
|
||||||
}
|
}
|
||||||
|
this.working--;
|
||||||
|
return Promise.resolve(pageNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetching = false;
|
|
||||||
window.setTimeout(() => { this.updater(); }, 10);
|
|
||||||
|
|
||||||
if (response.total <= (pageNumber - 1) * response.pageSize) {
|
if (response.total <= (pageNumber - 1) * response.pageSize) {
|
||||||
events.notify(events.Info, 'No data to show');
|
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--;
|
||||||
|
return Promise.reject();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue