/**
* @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));
|