diff options
Diffstat (limited to 'client/js/views/endless_page_view.js')
| -rw-r--r-- | client/js/views/endless_page_view.js | 111 |
1 files changed, 88 insertions, 23 deletions
diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js index f94c371..a4cb420 100644 --- a/client/js/views/endless_page_view.js +++ b/client/js/views/endless_page_view.js @@ -38,18 +38,26 @@ class EndlessPageView { this.defaultLimit = parseInt(ctx.parameters.limit || ctx.defaultLimit); const initialOffset = parseInt(ctx.parameters.offset || 0); - this._loadPage(ctx, initialOffset, this.defaultLimit, true).then( - (pageNode) => { - if (initialOffset !== 0) { - pageNode.scrollIntoView(); + if (this._isCacheValid(ctx)) { + this._loadCachedPages(ctx); + } else { + this._clearCache(ctx); + this._loadPage(ctx, initialOffset, this.defaultLimit, true).then( + (pageNode) => { + if (initialOffset !== 0) { + pageNode.scrollIntoView(); + } } - } - ); + ); + } this._timeout = window.setInterval(() => { window.requestAnimationFrame(() => { this._probePageLoad(ctx); this._syncUrl(ctx); + if (this._shouldUseCache(ctx)) { + ctx.browserState.scrollY = window.scrollY; + } }); }, 250); @@ -72,12 +80,7 @@ class EndlessPageView { return this._hostNode.querySelector(".pages-holder"); } - _destroy() { - window.clearInterval(this._timeout); - this._active = false; - } - - _syncUrl(ctx) { + get _topPageNode() { let topPageNode = null; let element = document.elementFromPoint( window.innerWidth / 2, @@ -90,18 +93,35 @@ class EndlessPageView { } element = element.parentNode; } + return topPageNode; + } + + _destroy() { + window.clearInterval(this._timeout); + this._active = false; + } + + _syncUrl(ctx) { + const topPageNode = this._topPageNode; if (!topPageNode) { return; } let topOffset = parseInt(topPageNode.getAttribute("data-offset")); let topLimit = parseInt(topPageNode.getAttribute("data-limit")); if (topOffset !== this.currentOffset) { + const path = ctx.getClientUrlForPage( + topOffset, + topLimit === ctx.defaultLimit ? null : topLimit + ); + if (this._shouldUseCache(ctx)) { + // We only scrolled, so we should continue using the same cache entry; + // Update the cache path so it's not invalidated: + ctx.browserState.pageCache.path = "/" + path; + } router.replace( - ctx.getClientUrlForPage( - topOffset, - topLimit === ctx.defaultLimit ? null : topLimit - ), - ctx.state, + path, + // ctx here is not "real" context, it's the object from _syncPageController() + ctx.browserState, false ); this.currentOffset = topOffset; @@ -116,11 +136,9 @@ class EndlessPageView { if (this.totalRecords === null) { return; } + const scrollThreshold = this._topPageNode.scrollHeight * 0.2; - if ( - this.minOffsetShown > 0 && - isScrolledIntoView(this.topPageGuardNode) - ) { + if (this.minOffsetShown > 0 && window.scrollY < scrollThreshold) { this._loadPage( ctx, this.minOffsetShown - this.defaultLimit, @@ -129,14 +147,51 @@ class EndlessPageView { ); } + const pageBottom = this._pagesHolderNode.getBoundingClientRect().bottom; if ( this.maxOffsetShown < this.totalRecords && - isScrolledIntoView(this.bottomPageGuardNode) + pageBottom < window.innerHeight + scrollThreshold ) { this._loadPage(ctx, this.maxOffsetShown, this.defaultLimit, true); } } + _shouldUseCache(ctx) { + return ctx.browserState !== undefined && ctx.browserState != null && + ctx.readPageFromCache !== undefined; + } + + _isCacheValid(ctx) { + if (!this._shouldUseCache(ctx)) return false; + const cache = ctx.browserState.pageCache; + return cache !== null && cache !== undefined && + cache.path == history.state.path && + cache.pages !== undefined && cache.pages !== null; + } + + _clearCache(ctx) { + if (!this._shouldUseCache(ctx)) return; + ctx.browserState.pageCache = {path: history.state.path, pages: {}}; + } + + _loadCachedPages(ctx) { + if (!this._shouldUseCache(ctx)) return; + // k-v map of page offset to raw response + const pages = ctx.browserState.pageCache.pages || {}; + window.requestAnimationFrame(() => { + for (const [offset, data] of Object.entries(pages)) { + const response = { + offset: data.offset, + limit: data.limit, + total: data.total, + results: ctx.readPageFromCache(data.raw_data) + }; + this._renderPage(ctx, true, response); + } + window.scroll(0, ctx.browserState.scrollY || 0); + }); + } + _loadPage(ctx, offset, limit, append) { this._runningRequests++; return new Promise((resolve, reject) => { @@ -146,6 +201,16 @@ class EndlessPageView { this._runningRequests--; return Promise.reject(); } + if (this._shouldUseCache(ctx)) { + // Need to extract raw_data, otherwise it can't be stored in history + const pages = ctx.browserState.pageCache.pages || {}; + pages[offset] = { + offset: response.offset, + limit: response.limit, + total: response.total, + raw_data: response.results.raw_data, + }; + } window.requestAnimationFrame(() => { let pageNode = this._renderPage(ctx, append, response); this._runningRequests--; @@ -190,7 +255,7 @@ class EndlessPageView { } if ( response.offset + response.results.length > - this.maxOffsetShown || + this.maxOffsetShown || this.maxOffsetShown === null ) { this.maxOffsetShown = |