From b7a67fc01cb63247f02da8e58f992769f8a6ec6a Mon Sep 17 00:00:00 2001 From: rr- Date: Tue, 12 Apr 2016 23:49:46 +0200 Subject: [PATCH] views/paging: add endless pager --- client/html/endless_pager.hbs | 4 + client/html/endless_pager_page.hbs | 4 + client/js/controllers/page_controller.js | 21 +++- client/js/controllers/settings_controller.js | 1 + client/js/controllers/users_controller.js | 9 +- client/js/events.js | 1 + client/js/util/polyfill.js | 8 ++ client/js/views/endless_page_view.js | 101 +++++++++++++++++++ client/js/views/manual_page_view.js | 5 +- 9 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 client/html/endless_pager.hbs create mode 100644 client/html/endless_pager_page.hbs create mode 100644 client/js/views/endless_page_view.js diff --git a/client/html/endless_pager.hbs b/client/html/endless_pager.hbs new file mode 100644 index 00000000..b7346834 --- /dev/null +++ b/client/html/endless_pager.hbs @@ -0,0 +1,4 @@ +
+
+
+
diff --git a/client/html/endless_pager_page.hbs b/client/html/endless_pager_page.hbs new file mode 100644 index 00000000..4f253ff2 --- /dev/null +++ b/client/html/endless_pager_page.hbs @@ -0,0 +1,4 @@ +
+

Page {{this.page}} of {{this.totalPages}}

+
+
diff --git a/client/js/controllers/page_controller.js b/client/js/controllers/page_controller.js index 783f70b3..da30ec03 100644 --- a/client/js/controllers/page_controller.js +++ b/client/js/controllers/page_controller.js @@ -1,16 +1,33 @@ 'use strict'; -const api = require('../api.js'); +const events = require('../events.js'); +const settingsController = require('./settings_controller.js'); +const EndlessPageView = require('../views/endless_page_view.js'); const ManualPageView = require('../views/manual_page_view.js'); class PageController { constructor() { - this.pageView = new ManualPageView(); + events.listen(events.SettingsChange, () => { + this.update(); + }); + this.update(); + } + + update() { + if (settingsController.getSettings().endlessScroll) { + this.pageView = new EndlessPageView(); + } else { + this.pageView = new ManualPageView(); + } } run(ctx) { this.pageView.render(ctx); } + + stop() { + this.pageView.unrender(); + } } module.exports = new PageController(); diff --git a/client/js/controllers/settings_controller.js b/client/js/controllers/settings_controller.js index 731ea2da..51fe8bb5 100644 --- a/client/js/controllers/settings_controller.js +++ b/client/js/controllers/settings_controller.js @@ -25,6 +25,7 @@ class SettingsController { saveSettings(browsingSettings) { localStorage.setItem('settings', JSON.stringify(browsingSettings)); events.notify(events.Success, 'Settings saved'); + events.notify(events.SettingsChange); } getSettings(settings) { diff --git a/client/js/controllers/users_controller.js b/client/js/controllers/users_controller.js index c5046b0b..12e8899a 100644 --- a/client/js/controllers/users_controller.js +++ b/client/js/controllers/users_controller.js @@ -39,7 +39,14 @@ class UsersController { '/user/:name/delete', (ctx, next) => { this.loadUserRoute(ctx, next); }, (ctx, next) => { this.deleteUserRoute(ctx, next); }); - page.exit('/user/', (ctx, next) => { this.user = null; }); + page.exit(/\/users\/.*/, (ctx, next) => { + pageController.stop(); + next(); + }); + page.exit(/\/user\/.*/, (ctx, next) => { + this.user = null; + next(); + }); } listUsersRoute(ctx, next) { diff --git a/client/js/events.js b/client/js/events.js index d8532a07..16f44972 100644 --- a/client/js/events.js +++ b/client/js/events.js @@ -27,6 +27,7 @@ module.exports = { Error: 2, Info: 3, Authentication: 4, + SettingsChange: 5, notify: notify, listen: listen, diff --git a/client/js/util/polyfill.js b/client/js/util/polyfill.js index de0b67b1..5e6101f7 100644 --- a/client/js/util/polyfill.js +++ b/client/js/util/polyfill.js @@ -29,6 +29,14 @@ if (!Object.entries) { NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator]; // non standard +Node.prototype.prependChild = function(child) { + if (this.firstChild) { + this.insertBefore(child, this.firstChild); + } else { + this.appendChild(child); + } +}; + Promise.prototype.always = function(onResolveOrReject) { return this.then( onResolveOrReject, diff --git a/client/js/views/endless_page_view.js b/client/js/views/endless_page_view.js new file mode 100644 index 00000000..408e3d74 --- /dev/null +++ b/client/js/views/endless_page_view.js @@ -0,0 +1,101 @@ +'use strict'; + +const events = require('../events.js'); +const misc = require('../util/misc.js'); +const views = require('../util/views.js'); + +function removeConsecutiveDuplicates(a) { + return a.filter((item, pos, ary) => { + return !pos || item != ary[pos - 1]; + }); +} + +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 pagesHolder = source.querySelector('.pages-holder'); + views.listenToMessages(target); + views.showView(target, source); + + const threshold = window.innerHeight / 3; + + this.minPageShown = null; + this.maxPageShown = null; + this.totalPages = null; + this.fetching = false; + + this.updater = () => { + if (this.fetching || 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); + } else if (this.maxPageShown < this.totalPages && + window.scrollY + threshold > scrollHeight) { + this.loadPage(pagesHolder, ctx, this.maxPageShown + 1, true); + } + }; + + this.loadPage(pagesHolder, ctx, ctx.initialPage, true); + window.addEventListener('scroll', this.updater, true); + } + + unrender() { + window.removeEventListener('scroll', this.updater, true); + } + + loadPage(pagesHolder, ctx, currentPage, append) { + this.fetching = true; + + if (currentPage < this.minPageShown || this.minPageShown === null) { + this.minPageShown = currentPage; + } + if (currentPage > this.maxPageShown || this.maxPageShown === null) { + this.maxPageShown = currentPage; + } + + ctx.requestPage(currentPage).then(response => { + this.totalPages = Math.ceil(response.total / response.pageSize); + if (response.total) { + const page = this.pageTemplate({ + page: currentPage, + totalPages: this.totalPages, + }); + + let pageRendererCtx = response; + pageRendererCtx.target = page.querySelector( + '.page-content-holder'); + ctx.pageRenderer.render(pageRendererCtx); + + if (append) { + pagesHolder.appendChild(page); + } else { + pagesHolder.prependChild(page); + window.scroll( + window.scrollX, window.scrollY + page.offsetHeight); + } + } + + this.fetching = false; + this.updater(); + + if (response.total <= (currentPage - 1) * response.pageSize) { + events.notify(events.Info, 'No data to show'); + } + }, response => { + events.notify(events.Error, response.description); + }); + } +} + +module.exports = EndlessPageView; diff --git a/client/js/views/manual_page_view.js b/client/js/views/manual_page_view.js index 7a166e7e..736999f1 100644 --- a/client/js/views/manual_page_view.js +++ b/client/js/views/manual_page_view.js @@ -75,7 +75,7 @@ class ManualPageView { views.listenToMessages(target); views.showView(target, source); - if (response.total < (currentPage - 1) * response.pageSize) { + if (response.total <= (currentPage - 1) * response.pageSize) { events.notify(events.Info, 'No data to show'); } }, response => { @@ -84,6 +84,9 @@ class ManualPageView { events.notify(events.Error, response.description); }); } + + unrender() { + } } module.exports = ManualPageView;