'use strict'; const page = require('page'); const events = require('../events.js'); const views = require('../util/views.js'); function _formatUrl(url, page) { return url.replace('{page}', page); } class EndlessPageView { constructor() { this._holderTemplate = views.getTemplate('endless-pager'); this._pageTemplate = views.getTemplate('endless-pager-page'); } render(ctx) { const target = document.getElementById('content-holder'); const source = this._holderTemplate(); const pageHeaderHolder = source.querySelector('.page-header-holder'); const pagesHolder = source.querySelector('.pages-holder'); views.listenToMessages(source); views.showView(target, source); this._active = true; this._working = 0; ctx.headerContext.target = pageHeaderHolder; ctx.headerRenderer.render(ctx.headerContext); const threshold = window.innerHeight / 3; this.minPageShown = null; this.maxPageShown = null; this.totalPages = null; this.currentPage = null; this._updater = () => { 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) { page.replace( _formatUrl(ctx.clientUrl, topPageNumber), null, false, 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); } unrender() { this._active = false; window.removeEventListener('scroll', this._updater, true); window.removeEventListener('unload', this._scrollToTop, true); } _scrollToTop() { window.scroll(0, 0); } _loadPage(pagesHolder, ctx, pageNumber, append) { this._working++; return ctx.requestPage(pageNumber).then(response => { if (!this._active) { this._working--; return Promise.reject(); } this.totalPages = Math.ceil(response.total / response.pageSize); 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) { 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--; 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 => { events.notify(events.Error, response.description); this._working--; return Promise.reject(); }); } } module.exports = EndlessPageView;