You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Readarr/UI/JsLibraries/backbone.backgrid.paginator.js

340 lines
11 KiB

/*
backgrid-paginator
http://github.com/wyuenho/backgrid
Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
Licensed under the MIT @license.
*/
(function (_, Backbone, Backgrid) {
"use strict";
/**
PageHandle is a class that renders the actual page handles and reacts to
click events for pagination.
This class acts in two modes - control or discrete page handle modes. If
one of the `is*` flags is `true`, an instance of this class is under
control page handle mode. Setting a `pageIndex` to an instance of this
class under control mode has no effect and the correct page index will
always be inferred from the `is*` flag. Only one of the `is*` flags should
be set to `true` at a time. For example, an instance of this class cannot
simultaneously be a rewind control and a fast forward control. A `label`
and a `title` template or a string are required to be passed to the
constuctor under this mode. If a `title` template is provided, it __MUST__
accept a parameter `label`. When the `label` is provided to the `title`
template function, its result will be used to render the generated anchor's
title attribute.
If all of the `is*` flags is set to `false`, which is the default, an
instance of this class will be in discrete page handle mode. An instance
under this mode requires the `pageIndex` to be passed from the constructor
as an option and it __MUST__ be a 0-based index of the list of page numbers
to render. The constuctor will normalize the base to the same base the
underlying PageableCollection collection instance uses. A `label` is not
required under this mode, which will default to the equivalent 1-based page
index calculated from `pageIndex` and the underlying PageableCollection
instance. A provided `label` will still be honored however. The `title`
parameter is also not required under this mode, in which case the default
`title` template will be used. You are encouraged to provide your own
`title` template however if you wish to localize the title strings.
If this page handle represents the current page, an `active` class will be
placed on the root list element.
if this page handle is at the border of the list of pages, a `disabled`
class will be placed on the root list element.
Only page handles that are neither `active` nor `disabled` will respond to
click events and triggers pagination.
@class Backgrid.Extension.PageHandle
*/
var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
/** @property */
tagName: "li",
/** @property */
events: {
"click a": "changePage"
},
/**
@property {string|function(Object.<string, string>): string} title
The title to use for the `title` attribute of the generated page handle
anchor elements. It can be a string or an Underscore template function
that takes a mandatory `label` parameter.
*/
title: _.template('Page <%- label %>'),
/**
@property {boolean} isRewind Whether this handle represents a rewind
control
*/
isRewind: false,
/**
@property {boolean} isBack Whether this handle represents a back
control
*/
isBack: false,
/**
@property {boolean} isForward Whether this handle represents a forward
control
*/
isForward: false,
/**
@property {boolean} isFastForward Whether this handle represents a fast
forward control
*/
isFastForward: false,
/**
Initializer.
@param {Object} options
@param {Backbone.Collection} options.collection
@param {number} pageIndex 0-based index of the page number this handle
handles. This parameter will be normalized to the base the underlying
PageableCollection uses.
@param {string} [options.label] If provided it is used to render the
anchor text, otherwise the normalized pageIndex will be used
instead. Required if any of the `is*` flags is set to `true`.
@param {string} [options.title]
@param {boolean} [options.isRewind=false]
@param {boolean} [options.isBack=false]
@param {boolean} [options.isForward=false]
@param {boolean} [options.isFastForward=false]
*/
initialize: function (options) {
Backbone.View.prototype.initialize.apply(this, arguments);
var collection = this.collection;
var state = collection.state;
var currentPage = state.currentPage;
var firstPage = state.firstPage;
var lastPage = state.lastPage;
_.extend(this, _.pick(options,
["isRewind", "isBack", "isForward", "isFastForward"]));
var pageIndex;
if (this.isRewind) pageIndex = firstPage;
else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
else if (this.isFastForward) pageIndex = lastPage;
else {
pageIndex = +options.pageIndex;
pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
}
this.pageIndex = pageIndex;
if (((this.isRewind || this.isBack) && currentPage == firstPage) ||
((this.isForward || this.isFastForward) && currentPage == lastPage)) {
this.$el.addClass("disabled");
}
else if (!(this.isRewind ||
this.isBack ||
this.isForward ||
this.isFastForward) &&
currentPage == pageIndex) {
this.$el.addClass("active");
}
this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
var title = options.title || this.title;
this.title = _.isFunction(title) ? title({label: this.label}) : title;
},
/**
Renders a clickable anchor element under a list item.
*/
render: function () {
this.$el.empty();
var anchor = document.createElement("a");
anchor.href = '#';
if (this.title) anchor.title = this.title;
anchor.innerHTML = this.label;
this.el.appendChild(anchor);
this.delegateEvents();
return this;
},
/**
jQuery click event handler. Goes to the page this PageHandle instance
represents. No-op if this page handle is currently active or disabled.
*/
changePage: function (e) {
e.preventDefault();
var $el = this.$el;
if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
this.collection.getPage(this.pageIndex);
}
return this;
}
});
/**
Paginator is a Backgrid extension that renders a series of configurable
pagination handles. This extension is best used for splitting a large data
set across multiple pages. If the number of pages is larger then a
threshold, which is set to 10 by default, the page handles are rendered
within a sliding window, plus the rewind, back, forward and fast forward
control handles. The individual control handles can be turned off.
@class Backgrid.Extension.Paginator
*/
Backgrid.Extension.Paginator = Backbone.View.extend({
/** @property */
className: "backgrid-paginator",
/** @property */
windowSize: 10,
/**
@property {Object.<string, Object.<string, string>>} controls You can
disable specific control handles by omitting certain keys.
*/
controls: {
rewind: {
label: "《",
title: "First"
},
back: {
label: "〈",
title: "Previous"
},
forward: {
label: "〉",
title: "Next"
},
fastForward: {
label: "》",
title: "Last"
}
},
/**
@property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
class to use for rendering individual handles
*/
pageHandle: PageHandle,
/** @property */
goBackFirstOnSort: true,
/**
Initializer.
@param {Object} options
@param {Backbone.Collection} options.collection
@param {boolean} [options.controls]
@param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
@param {boolean} [options.goBackFirstOnSort=true]
*/
initialize: function (options) {
Backgrid.requireOptions(options, ["collection"]);
this.controls = options.controls || this.controls;
this.pageHandle = options.pageHandle || this.pageHandle;
var collection = this.collection;
this.listenTo(collection, "add", this.render);
this.listenTo(collection, "remove", this.render);
this.listenTo(collection, "reset", this.render);
if ((options.goBackFirstOnSort || this.goBackFirstOnSort) &&
collection.fullCollection) {
this.listenTo(collection.fullCollection, "sort", function () {
collection.getFirstPage();
});
}
},
_calculateWindow: function () {
var collection = this.collection;
var state = collection.state;
// convert all indices to 0-based here
var firstPage = state.firstPage;
var lastPage = +state.lastPage;
lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
var currentPage = Math.max(state.currentPage, state.firstPage);
currentPage = firstPage ? currentPage - 1 : currentPage;
var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
return [windowStart, windowEnd];
},
/**
Creates a list of page handle objects for rendering.
@return {Array.<Object>} an array of page handle objects hashes
*/
makeHandles: function () {
var handles = [];
var collection = this.collection;
var window = this._calculateWindow();
var winStart = window[0], winEnd = window[1];
for (var i = winStart; i < winEnd; i++) {
handles.push(new PageHandle({
collection: collection,
pageIndex: i
}));
}
var controls = this.controls;
_.each(["back", "rewind", "forward", "fastForward"], function (key) {
var value = controls[key];
if (value) {
var handleCtorOpts = {
collection: collection,
title: value.title,
label: value.label
};
handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
var handle = new PageHandle(handleCtorOpts);
if (key == "rewind" || key == "back") handles.unshift(handle);
else handles.push(handle);
}
});
return handles;
},
/**
Render the paginator handles inside an unordered list.
*/
render: function () {
this.$el.empty();
if (this.handles) {
for (var i = 0, l = this.handles.length; i < l; i++) {
this.handles[i].remove();
}
}
var handles = this.handles = this.makeHandles();
var ul = document.createElement("ul");
for (var i = 0; i < handles.length; i++) {
ul.appendChild(handles[i].render().el);
}
this.el.appendChild(ul);
return this;
}
});
}(_, Backbone, Backgrid));