Added endless scroll (closed #5)
The code for paged collections now feels like playing ping-pong with callbacks, and like I have no idea on who should render who. It works, though.
This commit is contained in:
parent
0828a0aa89
commit
12b43b1bb8
5 changed files with 170 additions and 108 deletions
|
@ -1,73 +1,101 @@
|
||||||
var App = App || {};
|
var App = App || {};
|
||||||
App.Presenters = App.Presenters || {};
|
App.Presenters = App.Presenters || {};
|
||||||
|
|
||||||
App.Presenters.PagedCollectionPresenter = function(_, util, promise, api, mousetrap, router) {
|
App.Presenters.PagedCollectionPresenter = function(
|
||||||
|
_,
|
||||||
|
jQuery,
|
||||||
|
util,
|
||||||
|
promise,
|
||||||
|
api,
|
||||||
|
mousetrap,
|
||||||
|
router,
|
||||||
|
browsingSettings) {
|
||||||
|
|
||||||
|
var endlessScroll = browsingSettings.getSettings().endlessScroll;
|
||||||
|
var scrollInterval;
|
||||||
|
var template;
|
||||||
|
var totalPages;
|
||||||
|
var forceClear;
|
||||||
|
|
||||||
var searchOrder;
|
|
||||||
var searchQuery;
|
|
||||||
var pageNumber;
|
var pageNumber;
|
||||||
|
var searchParams;
|
||||||
var baseUri;
|
var baseUri;
|
||||||
var backendUri;
|
var backendUri;
|
||||||
var renderCallback;
|
var updateCallback;
|
||||||
var failCallback;
|
var failCallback;
|
||||||
|
|
||||||
var template;
|
|
||||||
var pageSize;
|
|
||||||
var totalPages;
|
|
||||||
var totalRecords;
|
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
|
forceClear = !_.isEqual(args.searchParams, searchParams) || parseInt(args.page) !== pageNumber + 1;
|
||||||
|
searchParams = args.searchParams;
|
||||||
pageNumber = parseInt(args.page) || 1;
|
pageNumber = parseInt(args.page) || 1;
|
||||||
searchOrder = args.order;
|
|
||||||
searchQuery = args.query;
|
|
||||||
baseUri = args.baseUri;
|
baseUri = args.baseUri;
|
||||||
backendUri = args.backendUri;
|
backendUri = args.backendUri;
|
||||||
renderCallback = args.renderCallback;
|
updateCallback = args.updateCallback;
|
||||||
failCallback = args.failCallback;
|
failCallback = args.failCallback;
|
||||||
|
|
||||||
promise.wait(util.promiseTemplate('pager')).then(function(html) {
|
promise.wait(util.promiseTemplate('pager')).then(function(html) {
|
||||||
template = _.template(html);
|
template = _.template(html);
|
||||||
changePage(pageNumber);
|
softChangePage(pageNumber);
|
||||||
|
|
||||||
mousetrap.bind('a', prevPage);
|
if (!endlessScroll) {
|
||||||
mousetrap.bind('d', nextPage);
|
mousetrap.bind('a', function(e) {
|
||||||
|
if (!e.altKey && !e.ctrlKey) {
|
||||||
|
prevPage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mousetrap.bind('d', function(e) {
|
||||||
|
if (!e.altKey && !e.ctrlKey) {
|
||||||
|
nextPage();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function prevPage(e) {
|
function prevPage() {
|
||||||
if (e.altKey || e.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pageNumber > 1) {
|
if (pageNumber > 1) {
|
||||||
router.navigate(getPageChangeLink(pageNumber - 1));
|
changePage(pageNumber - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function nextPage(e) {
|
function nextPage() {
|
||||||
if (e.altKey || e.ctrlKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pageNumber < totalPages) {
|
if (pageNumber < totalPages) {
|
||||||
router.navigate(getPageChangeLink(pageNumber + 1));
|
changePage(pageNumber + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nextPageInplace() {
|
||||||
|
if (pageNumber < totalPages) {
|
||||||
|
changePageInplace(pageNumber + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changePageInplace(newPageNumber) {
|
||||||
|
router.navigateInplace(getPageChangeLink(newPageNumber));
|
||||||
|
}
|
||||||
|
|
||||||
function changePage(newPageNumber) {
|
function changePage(newPageNumber) {
|
||||||
|
router.navigate(getPageChangeLink(newPageNumber));
|
||||||
|
}
|
||||||
|
|
||||||
|
function softChangePage(newPageNumber) {
|
||||||
pageNumber = newPageNumber;
|
pageNumber = newPageNumber;
|
||||||
|
|
||||||
promise.wait(
|
promise.wait(
|
||||||
api.get(backendUri, {
|
api.get(backendUri, _.extend({}, searchParams, {page: pageNumber})))
|
||||||
order: searchOrder,
|
|
||||||
query: searchQuery,
|
|
||||||
page: pageNumber
|
|
||||||
}))
|
|
||||||
.then(function(response) {
|
.then(function(response) {
|
||||||
pageSize = response.json.pageSize;
|
var pageSize = response.json.pageSize;
|
||||||
totalRecords = response.json.totalRecords;
|
var totalRecords = response.json.totalRecords;
|
||||||
totalPages = Math.ceil(totalRecords / pageSize);
|
totalPages = Math.ceil(totalRecords / pageSize);
|
||||||
renderCallback({
|
|
||||||
|
var $target = updateCallback({
|
||||||
entities: response.json.data,
|
entities: response.json.data,
|
||||||
totalRecords: response.json.totalRecords});
|
totalRecords: totalRecords},
|
||||||
|
forceClear || !endlessScroll);
|
||||||
|
forceClear = false;
|
||||||
|
|
||||||
|
render($target);
|
||||||
}).fail(function(response) {
|
}).fail(function(response) {
|
||||||
if (typeof(failCallback) !== 'undefined') {
|
if (typeof(failCallback) !== 'undefined') {
|
||||||
failCallback(response);
|
failCallback(response);
|
||||||
|
@ -78,6 +106,29 @@ App.Presenters.PagedCollectionPresenter = function(_, util, promise, api, mouset
|
||||||
}
|
}
|
||||||
|
|
||||||
function render($target) {
|
function render($target) {
|
||||||
|
var pages = getVisiblePages();
|
||||||
|
|
||||||
|
if (!endlessScroll) {
|
||||||
|
$target.find('.pager').remove();
|
||||||
|
$target.append(template({
|
||||||
|
pages: pages,
|
||||||
|
pageNumber: pageNumber,
|
||||||
|
link: getPageChangeLink,
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
var $scroller = jQuery('<div/>');
|
||||||
|
window.clearInterval(scrollInterval);
|
||||||
|
scrollInterval = window.setInterval(function() {
|
||||||
|
if ($scroller.is(':visible')) {
|
||||||
|
nextPageInplace();
|
||||||
|
window.clearInterval(scrollInterval);
|
||||||
|
}
|
||||||
|
}, 50);
|
||||||
|
$target.append($scroller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVisiblePages() {
|
||||||
var pages = [1, totalPages];
|
var pages = [1, totalPages];
|
||||||
var pagesAroundCurrent = 2;
|
var pagesAroundCurrent = 2;
|
||||||
for (var i = -pagesAroundCurrent; i <= pagesAroundCurrent; i ++) {
|
for (var i = -pagesAroundCurrent; i <= pagesAroundCurrent; i ++) {
|
||||||
|
@ -96,46 +147,25 @@ App.Presenters.PagedCollectionPresenter = function(_, util, promise, api, mouset
|
||||||
return !pos || item !== pages[pos - 1];
|
return !pos || item !== pages[pos - 1];
|
||||||
});
|
});
|
||||||
|
|
||||||
$target.html(template({
|
return pages;
|
||||||
pages: pages,
|
|
||||||
pageNumber: pageNumber,
|
|
||||||
link: getPageChangeLink,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSearchQueryChangeLink(newSearchQuery) {
|
function getSearchChangeLink(newSearchParams) {
|
||||||
return util.compileComplexRouteArgs(baseUri, {
|
return util.compileComplexRouteArgs(baseUri, _.extend({}, searchParams, newSearchParams, {page: 1}));
|
||||||
page: 1,
|
|
||||||
order: searchOrder,
|
|
||||||
query: newSearchQuery,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSearchOrderChangeLink(newSearchOrder) {
|
|
||||||
return util.compileComplexRouteArgs(baseUri, {
|
|
||||||
page: 1,
|
|
||||||
order: newSearchOrder,
|
|
||||||
query: searchQuery,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPageChangeLink(newPageNumber) {
|
function getPageChangeLink(newPageNumber) {
|
||||||
return util.compileComplexRouteArgs(baseUri, {
|
return util.compileComplexRouteArgs(baseUri, _.extend({}, searchParams, {page: newPageNumber}));
|
||||||
page: newPageNumber,
|
|
||||||
order: searchOrder,
|
|
||||||
query: searchQuery,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
render: render,
|
render: render,
|
||||||
changePage: changePage,
|
changePage: changePage,
|
||||||
getSearchQueryChangeLink: getSearchQueryChangeLink,
|
getSearchChangeLink: getSearchChangeLink,
|
||||||
getSearchOrderChangeLink: getSearchOrderChangeLink,
|
|
||||||
getPageChangeLink: getPageChangeLink
|
getPageChangeLink: getPageChangeLink
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
App.DI.register('pagedCollectionPresenter', ['_', 'util', 'promise', 'api', 'mousetrap', 'router'], App.Presenters.PagedCollectionPresenter);
|
App.DI.register('pagedCollectionPresenter', ['_', 'jQuery', 'util', 'promise', 'api', 'mousetrap', 'router', 'browsingSettings'], App.Presenters.PagedCollectionPresenter);
|
||||||
|
|
|
@ -13,33 +13,41 @@ App.Presenters.UserListPresenter = function(
|
||||||
messagePresenter) {
|
messagePresenter) {
|
||||||
|
|
||||||
var $el = jQuery('#content');
|
var $el = jQuery('#content');
|
||||||
var template;
|
var listTemplate;
|
||||||
var userList = [];
|
var itemTemplate;
|
||||||
var activeSearchOrder;
|
|
||||||
|
|
||||||
function init(args) {
|
function init(args) {
|
||||||
topNavigationPresenter.select('users');
|
topNavigationPresenter.select('users');
|
||||||
topNavigationPresenter.changeTitle('Users');
|
topNavigationPresenter.changeTitle('Users');
|
||||||
|
|
||||||
promise.wait(util.promiseTemplate('user-list')).then(function(html) {
|
promise.waitAll(
|
||||||
template = _.template(html);
|
util.promiseTemplate('user-list'),
|
||||||
initPaginator(args);
|
util.promiseTemplate('user-list-item')).then(function(listHtml, itemHtml) {
|
||||||
|
listTemplate = _.template(listHtml);
|
||||||
|
itemTemplate = _.template(itemHtml);
|
||||||
|
|
||||||
|
render();
|
||||||
|
reinit(args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function initPaginator(args) {
|
function reinit(args) {
|
||||||
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
var searchArgs = util.parseComplexRouteArgs(args.searchArgs);
|
||||||
searchArgs.order = searchArgs.order || 'name';
|
searchArgs.order = searchArgs.order || 'name';
|
||||||
activeSearchOrder = searchArgs.order;
|
|
||||||
|
|
||||||
|
updateActiveOrder(searchArgs.order);
|
||||||
|
initPaginator(searchArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
function initPaginator(searchArgs) {
|
||||||
pagedCollectionPresenter.init({
|
pagedCollectionPresenter.init({
|
||||||
page: searchArgs.page,
|
page: searchArgs.page,
|
||||||
order: searchArgs.order,
|
searchParams: {order: searchArgs.order},
|
||||||
baseUri: '#/users',
|
baseUri: '#/users',
|
||||||
backendUri: '/users',
|
backendUri: '/users',
|
||||||
renderCallback: function updateCollection(data) {
|
updateCallback: function(data, clear) {
|
||||||
userList = data.entities;
|
renderUsers(data.entities, clear);
|
||||||
render();
|
return $el.find('.pagination-content');
|
||||||
},
|
},
|
||||||
failCallback: function(response) {
|
failCallback: function(response) {
|
||||||
$el.empty();
|
$el.empty();
|
||||||
|
@ -48,28 +56,44 @@ App.Presenters.UserListPresenter = function(
|
||||||
}
|
}
|
||||||
|
|
||||||
function render() {
|
function render() {
|
||||||
$el.html(template({
|
$el.html(listTemplate());
|
||||||
userList: userList,
|
|
||||||
formatRelativeTime: util.formatRelativeTime,
|
|
||||||
}));
|
|
||||||
$el.find('.order a').click(orderLinkClicked);
|
$el.find('.order a').click(orderLinkClicked);
|
||||||
$el.find('.order [data-order="' + activeSearchOrder + '"]').parent('li').addClass('active');
|
}
|
||||||
|
|
||||||
var $pager = $el.find('.pager');
|
function updateActiveOrder(activeOrder) {
|
||||||
pagedCollectionPresenter.render($pager);
|
$el.find('.order li').removeClass('active');
|
||||||
|
$el.find('.order [data-order="' + activeOrder + '"]').parent('li').addClass('active');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderUsers(users, clear) {
|
||||||
|
var $target = $el.find('.users');
|
||||||
|
|
||||||
|
var all = '';
|
||||||
|
_.each(users, function(user) {
|
||||||
|
all += itemTemplate({
|
||||||
|
user: user,
|
||||||
|
formatRelativeTime: util.formatRelativeTime,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (clear) {
|
||||||
|
$target.html(all);
|
||||||
|
} else {
|
||||||
|
$target.append(all);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function orderLinkClicked(e) {
|
function orderLinkClicked(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var $orderLink = jQuery(this);
|
var $orderLink = jQuery(this);
|
||||||
activeSearchOrder = $orderLink.attr('data-order');
|
var activeSearchOrder = $orderLink.attr('data-order');
|
||||||
router.navigate(pagedCollectionPresenter.getSearchOrderChangeLink(activeSearchOrder));
|
router.navigate(pagedCollectionPresenter.getSearchChangeLink({order: activeSearchOrder}));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
init: init,
|
init: init,
|
||||||
reinit: initPaginator,
|
reinit: reinit,
|
||||||
render: render
|
render: render,
|
||||||
};
|
};
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,6 +14,15 @@ App.Router = function(pathJs, _, jQuery, util, appState, presenterManager) {
|
||||||
window.location.href = url;
|
window.location.href = url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function navigateInplace(url) {
|
||||||
|
if ('replaceState' in history) {
|
||||||
|
history.replaceState('', '', url);
|
||||||
|
pathJs.dispatch(document.location.hash);
|
||||||
|
} else {
|
||||||
|
navigate(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
pathJs.listen();
|
pathJs.listen();
|
||||||
}
|
}
|
||||||
|
@ -54,6 +63,7 @@ App.Router = function(pathJs, _, jQuery, util, appState, presenterManager) {
|
||||||
return {
|
return {
|
||||||
start: start,
|
start: start,
|
||||||
navigate: navigate,
|
navigate: navigate,
|
||||||
|
navigateInplace: navigateInplace,
|
||||||
navigateToMainPage: navigateToMainPage,
|
navigateToMainPage: navigateToMainPage,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
18
public_html/templates/user-list-item.tpl
Normal file
18
public_html/templates/user-list-item.tpl
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
<li class="user">
|
||||||
|
<a href="#/user/<%= user.name %>">
|
||||||
|
<img src="/api/users/<%= user.name %>/avatar/80" alt="<%= user.name %>"/>
|
||||||
|
</a>
|
||||||
|
<div class="details">
|
||||||
|
<h1>
|
||||||
|
<a href="#/user/<%= user.name %>">
|
||||||
|
<%= user.name %>
|
||||||
|
</a>
|
||||||
|
</h1>
|
||||||
|
<div class="date-joined" title="<%= user.registrationTime %>">
|
||||||
|
Joined: <%= formatRelativeTime(user.registrationTime) %>
|
||||||
|
</div>
|
||||||
|
<div class="date-seen">
|
||||||
|
Last seen: <%= formatRelativeTime(user.lastLoginTime) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
|
@ -14,28 +14,8 @@
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
<div class="pagination-content">
|
||||||
<ul class="users">
|
<ul class="users">
|
||||||
<% _.each(userList, function(user) { %>
|
|
||||||
<li class="user">
|
|
||||||
<a href="#/user/<%= user.name %>">
|
|
||||||
<img src="/api/users/<%= user.name %>/avatar/80" alt="<%= user.name %>"/>
|
|
||||||
</a>
|
|
||||||
<div class="details">
|
|
||||||
<h1>
|
|
||||||
<a href="#/user/<%= user.name %>">
|
|
||||||
<%= user.name %>
|
|
||||||
</a>
|
|
||||||
</h1>
|
|
||||||
<div class="date-joined" title="<%= user.registrationTime %>">
|
|
||||||
Joined: <%= formatRelativeTime(user.registrationTime) %>
|
|
||||||
</div>
|
|
||||||
<div class="date-seen">
|
|
||||||
Last seen: <%= formatRelativeTime(user.lastLoginTime) %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<% }); %>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
</div>
|
||||||
<div class="pager"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue