blob: 3941c2dffd18a726d7de2395439d86fa6bad88c8 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function (callback) {
if (typeof define === 'function' && define.amd) {
define(['core/AbstractWidget'], callback);
}
else {
callback();
}
}(function () {
(function ($) {
/**
* A pager widget for jQuery.
*
* <p>Heavily inspired by the Ruby on Rails will_paginate gem.</p>
*
* @expects this.target to be a list.
* @class PagerWidget
* @augments AjaxSolr.AbstractWidget
* @todo Don't use the manager to send the request. Request only the results,
* not the facets. Update only itself and the results widget.
*/
AjaxSolr.PagerWidget = AjaxSolr.AbstractWidget.extend(
/** @lends AjaxSolr.PagerWidget.prototype */
{
/**
* @param {Object} [attributes]
* @param {Number} [attributes.innerWindow] How many links are shown around
* the current page. Defaults to 4.
* @param {Number} [attributes.outerWindow] How many links are around the
* first and the last page. Defaults to 1.
* @param {String} [attributes.prevLabel] The previous page link label.
* Defaults to "&laquo; Previous".
* @param {String} [attributes.nextLabel] The next page link label. Defaults
* to "Next &raquo;".
* @param {String} [attributes.separator] Separator between pagination links.
* Defaults to " ".
*/
constructor: function (attributes) {
AjaxSolr.PagerWidget.__super__.constructor.apply(this, arguments);
AjaxSolr.extend(this, {
innerWindow: 4,
outerWindow: 1,
prevLabel: '&laquo; Previous',
nextLabel: 'Next &raquo;',
separator: ' ',
// The current page number.
currentPage: null,
// The total number of pages.
totalPages: null
}, attributes);
},
/**
* @returns {String} The gap in page links, which is represented by:
* <span class="pager-gap">&hellip;</span>
*/
gapMarker: function () {
return '<span class="pager-gap">&hellip;</span>';
},
/**
* @returns {Array} The links for the visible page numbers.
*/
windowedLinks: function () {
var links = [];
var prev = null;
visible = this.visiblePageNumbers();
for (var i = 0, l = visible.length; i < l; i++) {
if (prev && visible[i] > prev + 1) links.push(this.gapMarker());
links.push(this.pageLinkOrSpan(visible[i], [ 'pager-current' ]));
prev = visible[i];
}
return links;
},
/**
* @returns {Array} The visible page numbers according to the window options.
*/
visiblePageNumbers: function () {
var windowFrom = this.currentPage - this.innerWindow;
var windowTo = this.currentPage + this.innerWindow;
// If the window is truncated on one side, make the other side longer
if (windowTo > this.totalPages) {
windowFrom = Math.max(0, windowFrom - (windowTo - this.totalPages));
windowTo = this.totalPages;
}
if (windowFrom < 1) {
windowTo = Math.min(this.totalPages, windowTo + (1 - windowFrom));
windowFrom = 1;
}
var visible = [];
// Always show the first page
visible.push(1);
// Don't add inner window pages twice
for (var i = 2; i <= Math.min(1 + this.outerWindow, windowFrom - 1); i++) {
visible.push(i);
}
// If the gap is just one page, close the gap
if (1 + this.outerWindow == windowFrom - 2) {
visible.push(windowFrom - 1);
}
// Don't add the first or last page twice
for (var i = Math.max(2, windowFrom); i <= Math.min(windowTo, this.totalPages - 1); i++) {
visible.push(i);
}
// If the gap is just one page, close the gap
if (this.totalPages - this.outerWindow == windowTo + 2) {
visible.push(windowTo + 1);
}
// Don't add inner window pages twice
for (var i = Math.max(this.totalPages - this.outerWindow, windowTo + 1); i < this.totalPages; i++) {
visible.push(i);
}
// Always show the last page, unless it's the first page
if (this.totalPages > 1) {
visible.push(this.totalPages);
}
return visible;
},
/**
* @param {Number} page A page number.
* @param {String} classnames CSS classes to add to the page link.
* @param {String} text The inner HTML of the page link (optional).
* @returns The link or span for the given page.
*/
pageLinkOrSpan: function (page, classnames, text) {
text = text || page;
if (page && page != this.currentPage) {
return $('<a href="#"></a>').html(text).attr('rel', this.relValue(page)).addClass(classnames[1]).click(this.clickHandler(page));
}
else {
return $('<span></span>').html(text).addClass(classnames.join(' '));
}
},
/**
* @param {Number} page A page number.
* @returns {Function} The click handler for the page link.
*/
clickHandler: function (page) {
var self = this;
return function () {
self.manager.store.get('start').val((page - 1) * self.perPage());
self.doRequest();
return false;
}
},
/**
* @param {Number} page A page number.
* @returns {String} The <tt>rel</tt> attribute for the page link.
*/
relValue: function (page) {
switch (page) {
case this.previousPage():
return 'prev' + (page == 1 ? 'start' : '');
case this.nextPage():
return 'next';
case 1:
return 'start';
default:
return '';
}
},
/**
* @returns {Number} The page number of the previous page or null if no previous page.
*/
previousPage: function () {
return this.currentPage > 1 ? (this.currentPage - 1) : null;
},
/**
* @returns {Number} The page number of the next page or null if no next page.
*/
nextPage: function () {
return this.currentPage < this.totalPages ? (this.currentPage + 1) : null;
},
/**
* An abstract hook for child implementations.
*
* @param {Number} perPage The number of items shown per results page.
* @param {Number} offset The index in the result set of the first document to render.
* @param {Number} total The total number of documents in the result set.
*/
renderHeader: function (perPage, offset, total) {},
/**
* Render the pagination links.
*
* @param {Array} links The links for the visible page numbers.
*/
renderLinks: function (links) {
if (this.totalPages) {
links.unshift(this.pageLinkOrSpan(this.previousPage(), [ 'pager-disabled', 'pager-prev' ], this.prevLabel));
links.push(this.pageLinkOrSpan(this.nextPage(), [ 'pager-disabled', 'pager-next' ], this.nextLabel));
var $target = $(this.target);
$target.empty();
for (var i = 0, l = links.length; i < l; i++) {
var $li = $('<li></li>');
if (this.separator && i > 0) {
$li.append(this.separator);
}
$target.append($li.append(links[i]));
}
}
},
/**
* @returns {Number} The number of results to display per page.
*/
perPage: function () {
return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.rows || this.manager.store.get('rows').val() || 10);
},
/**
* @returns {Number} The Solr offset parameter's value.
*/
getOffset: function () {
return parseInt(this.manager.response.responseHeader && this.manager.response.responseHeader.params && this.manager.response.responseHeader.params.start || this.manager.store.get('start').val() || 0);
},
afterRequest: function () {
var perPage = this.perPage();
var offset = this.getOffset();
var total = parseInt(this.manager.response.response.numFound);
// Normalize the offset to a multiple of perPage.
offset = offset - offset % perPage;
this.currentPage = Math.ceil((offset + 1) / perPage);
this.totalPages = Math.ceil(total / perPage);
$(this.target).empty();
this.renderLinks(this.windowedLinks());
this.renderHeader(perPage, offset, total);
}
});
})(jQuery);
}));