340 lines
8.8 KiB
JavaScript
340 lines
8.8 KiB
JavaScript
/**
|
|
* @file js/classes/features/InfiniteScrollingFeature.js
|
|
*
|
|
* Copyright (c) 2016-2021 Simon Fraser University
|
|
* Copyright (c) 2000-2021 John Willinsky
|
|
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
|
|
*
|
|
* @class InfiniteScrollingFeature
|
|
* @ingroup js_classes_features
|
|
*
|
|
* @brief Feature that implements infinite scrolling on grids.
|
|
* It doesn't support category grids.
|
|
*/
|
|
(function($) {
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
* @inheritDoc
|
|
* @extends $.pkp.classes.features.GeneralPagingFeature
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature =
|
|
function(gridHandler, options) {
|
|
this.parent(gridHandler, options);
|
|
};
|
|
$.pkp.classes.Helper.inherits(
|
|
$.pkp.classes.features.InfiniteScrollingFeature,
|
|
$.pkp.classes.features.GeneralPagingFeature);
|
|
|
|
|
|
//
|
|
// Private properties
|
|
//
|
|
/**
|
|
* The scrollable element.
|
|
* @private
|
|
* @type {jQueryObject}
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
$scrollableElement_ = $();
|
|
|
|
|
|
/**
|
|
* The scrolling observer callback function.
|
|
* @private
|
|
* @type {Function}
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
observeScrollCallback_ = function() {};
|
|
|
|
|
|
//
|
|
// Extended methods from GeneralPagingFeature
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.init =
|
|
function() {
|
|
var $scrollableElement = $('div.scrollable', this.getGridHtmlElement());
|
|
if (!$scrollableElement.length) {
|
|
this.gridHandler.publishEvent('pkpObserveScrolling');
|
|
this.gridHandler.publishEvent('pkpRemoveScrollingObserver');
|
|
}
|
|
this.$scrollableElement_ = $scrollableElement;
|
|
this.observeScrollCallback_ = this.gridHandler.callbackWrapper(
|
|
this.observeScroll_, this);
|
|
this.addScrollHandler_();
|
|
this.fixGridHeight_();
|
|
this.addPagingDataToRows_();
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addFeatureHtml =
|
|
function($gridElement, options) {
|
|
var castOptions = /** @type {{pagingMarkup: string?,
|
|
loadingContainer: string?}} */ (options);
|
|
$gridElement.append(castOptions.pagingMarkup);
|
|
$gridElement.find('.pkp_linkaction_moreItems')
|
|
.click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));
|
|
};
|
|
|
|
|
|
//
|
|
// Hooks implementation.
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.refreshGrid =
|
|
function(opt_elementId) {
|
|
var options = this.getOptions(), params, $firstRow, $lastRow, page, $gridRow,
|
|
elementId;
|
|
|
|
params = this.gridHandler.getFetchExtraParams();
|
|
params[options.pageParamName] = options.currentPage;
|
|
|
|
if (opt_elementId && opt_elementId !==
|
|
$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID) {
|
|
// We need to make sure we pass the right page for the element.
|
|
elementId = (/** @type {number} */ (opt_elementId));
|
|
$gridRow = this.gridHandler.getRowByDataId(elementId);
|
|
if ($gridRow.length == 1) {
|
|
params[options.pageParamName] = Number($gridRow.attr('data-paging'));
|
|
}
|
|
}
|
|
|
|
params[options.itemsPerPageParamName] = options.currentItemsPerPage;
|
|
|
|
this.setGridParams(params);
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
replaceElementResponseHandler = function(handledJsonData) {
|
|
var pagingInfo, options, castJsonData, rowMarkup;
|
|
options = this.getOptions();
|
|
castJsonData = /** @type {{pagingInfo: Object,
|
|
deletedRowReplacement: string}} */
|
|
(handledJsonData);
|
|
|
|
if (castJsonData.deletedRowReplacement != undefined) {
|
|
rowMarkup = handledJsonData.deletedRowReplacement;
|
|
this.gridHandler.insertOrReplaceElement(rowMarkup);
|
|
this.updatePagingDataInAllRows_();
|
|
}
|
|
|
|
this.addScrollHandler_();
|
|
|
|
if (castJsonData.pagingInfo != undefined) {
|
|
pagingInfo = handledJsonData.pagingInfo;
|
|
this.setOptions(pagingInfo);
|
|
|
|
if (pagingInfo.pagingMarkup != undefined) {
|
|
$('div.gridPagingScrolling', this.getGridHtmlElement()).
|
|
replaceWith(pagingInfo.pagingMarkup);
|
|
}
|
|
}
|
|
|
|
this.addPagingDataToRows_();
|
|
|
|
this.toggleLoadingContainer_();
|
|
|
|
this.getGridHtmlElement().find('.pkp_linkaction_moreItems').
|
|
click(this.gridHandler.callbackWrapper(this.loadMoreItems_, this));
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
//
|
|
// Private helper methods.
|
|
//
|
|
/**
|
|
* Scroll handler to detect when it's time to request more rows.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {HTMLElement} sourceElement
|
|
* @param {Event} event
|
|
* @return {boolean}
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.observeScroll_ =
|
|
function(sourceElement, event) {
|
|
var options = this.getOptions(), sourceElementHeight,
|
|
bottomLimit, windowDimensions;
|
|
if (options.itemsTotal == this.gridHandler.getRows().length) {
|
|
return false;
|
|
}
|
|
|
|
if (!this.getGridHtmlElement().is(':visible')) {
|
|
return false;
|
|
}
|
|
|
|
if ($(sourceElement).hasClass('scrollable')) {
|
|
sourceElementHeight = $(sourceElement).height();
|
|
bottomLimit = sourceElement.scrollHeight;
|
|
} else {
|
|
windowDimensions = $.pkp.controllers.SiteHandler.
|
|
prototype.getWindowDimensions();
|
|
sourceElementHeight = windowDimensions.height;
|
|
bottomLimit = this.getGridHtmlElement().offset().top +
|
|
this.getGridHtmlElement().height();
|
|
}
|
|
|
|
if (sourceElementHeight + $(sourceElement).scrollTop() >= bottomLimit) {
|
|
// Avoid multiple rows requests.
|
|
if (this.$scrollableElement_.length) {
|
|
this.$scrollableElement_.unbind('scroll');
|
|
} else {
|
|
this.getGridHtmlElement().trigger('pkpRemoveScrollingObserver',
|
|
[this.observeScrollCallback_]);
|
|
}
|
|
|
|
this.loadMoreItems_();
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Fix the grid height to acomodate the number of initial visible rows.
|
|
*
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.fixGridHeight_ =
|
|
function() {
|
|
var $scrollableDivs = $('div.scrollable', this.getGridHtmlElement()),
|
|
index, limit, $div, timer, length;
|
|
|
|
if ($scrollableDivs.length > 0) {
|
|
timer = setInterval(function() {
|
|
if ($scrollableDivs.is(':visible')) {
|
|
clearInterval(timer);
|
|
length = $scrollableDivs.length;
|
|
for (index = 0, limit = length; index < limit; index++) {
|
|
$div = $($scrollableDivs[index]);
|
|
if ($div.get(0).scrollHeight > $div.height()) {
|
|
$div.css('max-height', $div.get(0).scrollHeight - 10);
|
|
}
|
|
}
|
|
}
|
|
},300);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Add paging data to the respective rows.
|
|
*
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addPagingDataToRows_ =
|
|
function() {
|
|
var $rows, options = this.getOptions();
|
|
$rows = this.gridHandler.getRows().filter('tr:not([data-paging])');
|
|
$rows.attr('data-paging', options.currentPage);
|
|
};
|
|
|
|
|
|
/**
|
|
* Update paging data in all grid rows.
|
|
*
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
updatePagingDataInAllRows_ = function() {
|
|
var $rows, options = this.getOptions(), index, limit, page = 1,
|
|
itemsCount = 1;
|
|
$rows = this.gridHandler.getRows();
|
|
$rows.removeAttr('data-paging');
|
|
|
|
for (index = 0, limit = $rows.length; index < limit; index++) {
|
|
$($rows[index]).attr('data-paging', page);
|
|
itemsCount++;
|
|
if (itemsCount > options.currentItemsPerPage) {
|
|
itemsCount = 1;
|
|
page++;
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Add scroll handler to the grid element.
|
|
*
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.addScrollHandler_ =
|
|
function() {
|
|
var $scrollableElement = this.$scrollableElement_;
|
|
if ($scrollableElement.length) {
|
|
$scrollableElement.
|
|
scroll(this.observeScrollCallback_);
|
|
} else {
|
|
this.getGridHtmlElement().trigger('pkpObserveScrolling',
|
|
[this.observeScrollCallback_]);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Toggle the scrolling loading element.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {boolean=} opt_show
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
toggleLoadingContainer_ = function(opt_show) {
|
|
var $loadingElement =
|
|
this.getGridHtmlElement().find('div.gridPagingScrolling div.pkp_loading'),
|
|
$scrollableElement = this.$scrollableElement_,
|
|
scrollTop,
|
|
loadingHeight = $loadingElement.height(),
|
|
scrollTarget;
|
|
|
|
if (opt_show) {
|
|
this.getGridHtmlElement().addClass('loading');
|
|
scrollTop = $scrollableElement.scrollTop();
|
|
scrollTarget = /** @type {number} */ (scrollTop + loadingHeight);
|
|
$scrollableElement.scrollTop(scrollTarget);
|
|
} else {
|
|
this.getGridHtmlElement().removeClass('loading');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Trigger necessary actions for the grid to
|
|
* load next page items.
|
|
*
|
|
* @private
|
|
*
|
|
*/
|
|
$.pkp.classes.features.InfiniteScrollingFeature.prototype.
|
|
loadMoreItems_ = function() {
|
|
var options = this.getOptions();
|
|
|
|
// Show the loading icon.
|
|
this.toggleLoadingContainer_(true);
|
|
|
|
options.currentPage = Number($('tr.gridRow',
|
|
this.getGridHtmlElement()).last().attr('data-paging')) + 1;
|
|
this.getGridHtmlElement().trigger('dataChanged',
|
|
[$.pkp.controllers.grid.GridHandler.FETCH_ALL_ROWS_ID]);
|
|
};
|
|
|
|
|
|
}(jQuery));
|