PageRenderTime 57ms CodeModel.GetById 30ms RepoModel.GetById 0ms app.codeStats 0ms

/src/extensions/paginator/backgrid-paginator.js

https://github.com/antonyraj/backgrid
JavaScript | 339 lines | 160 code | 44 blank | 135 comment | 37 complexity | 05c0659c9e757fa62cc4f084f5a03eb1 MD5 | raw file
Possible License(s): MIT
  1. /*
  2. backgrid-paginator
  3. http://github.com/wyuenho/backgrid
  4. Copyright (c) 2013 Jimmy Yuen Ho Wong and contributors
  5. Licensed under the MIT @license.
  6. */
  7. (function (_, Backbone, Backgrid) {
  8. "use strict";
  9. /**
  10. PageHandle is a class that renders the actual page handles and reacts to
  11. click events for pagination.
  12. This class acts in two modes - control or discrete page handle modes. If
  13. one of the `is*` flags is `true`, an instance of this class is under
  14. control page handle mode. Setting a `pageIndex` to an instance of this
  15. class under control mode has no effect and the correct page index will
  16. always be inferred from the `is*` flag. Only one of the `is*` flags should
  17. be set to `true` at a time. For example, an instance of this class cannot
  18. simultaneously be a rewind control and a fast forward control. A `label`
  19. and a `title` template or a string are required to be passed to the
  20. constuctor under this mode. If a `title` template is provided, it __MUST__
  21. accept a parameter `label`. When the `label` is provided to the `title`
  22. template function, its result will be used to render the generated anchor's
  23. title attribute.
  24. If all of the `is*` flags is set to `false`, which is the default, an
  25. instance of this class will be in discrete page handle mode. An instance
  26. under this mode requires the `pageIndex` to be passed from the constructor
  27. as an option and it __MUST__ be a 0-based index of the list of page numbers
  28. to render. The constuctor will normalize the base to the same base the
  29. underlying PageableCollection collection instance uses. A `label` is not
  30. required under this mode, which will default to the equivalent 1-based page
  31. index calculated from `pageIndex` and the underlying PageableCollection
  32. instance. A provided `label` will still be honored however. The `title`
  33. parameter is also not required under this mode, in which case the default
  34. `title` template will be used. You are encouraged to provide your own
  35. `title` template however if you wish to localize the title strings.
  36. If this page handle represents the current page, an `active` class will be
  37. placed on the root list element.
  38. if this page handle is at the border of the list of pages, a `disabled`
  39. class will be placed on the root list element.
  40. Only page handles that are neither `active` nor `disabled` will respond to
  41. click events and triggers pagination.
  42. @class Backgrid.Extension.PageHandle
  43. */
  44. var PageHandle = Backgrid.Extension.PageHandle = Backbone.View.extend({
  45. /** @property */
  46. tagName: "li",
  47. /** @property */
  48. events: {
  49. "click a": "changePage"
  50. },
  51. /**
  52. @property {string|function(Object.<string, string>): string} title
  53. The title to use for the `title` attribute of the generated page handle
  54. anchor elements. It can be a string or an Underscore template function
  55. that takes a mandatory `label` parameter.
  56. */
  57. title: _.template('Page <%- label %>'),
  58. /**
  59. @property {boolean} isRewind Whether this handle represents a rewind
  60. control
  61. */
  62. isRewind: false,
  63. /**
  64. @property {boolean} isBack Whether this handle represents a back
  65. control
  66. */
  67. isBack: false,
  68. /**
  69. @property {boolean} isForward Whether this handle represents a forward
  70. control
  71. */
  72. isForward: false,
  73. /**
  74. @property {boolean} isFastForward Whether this handle represents a fast
  75. forward control
  76. */
  77. isFastForward: false,
  78. /**
  79. Initializer.
  80. @param {Object} options
  81. @param {Backbone.Collection} options.collection
  82. @param {number} pageIndex 0-based index of the page number this handle
  83. handles. This parameter will be normalized to the base the underlying
  84. PageableCollection uses.
  85. @param {string} [options.label] If provided it is used to render the
  86. anchor text, otherwise the normalized pageIndex will be used
  87. instead. Required if any of the `is*` flags is set to `true`.
  88. @param {string} [options.title]
  89. @param {boolean} [options.isRewind=false]
  90. @param {boolean} [options.isBack=false]
  91. @param {boolean} [options.isForward=false]
  92. @param {boolean} [options.isFastForward=false]
  93. */
  94. initialize: function (options) {
  95. Backbone.View.prototype.initialize.apply(this, arguments);
  96. var collection = this.collection;
  97. var state = collection.state;
  98. var currentPage = state.currentPage;
  99. var firstPage = state.firstPage;
  100. var lastPage = state.lastPage;
  101. _.extend(this, _.pick(options,
  102. ["isRewind", "isBack", "isForward", "isFastForward"]));
  103. var pageIndex;
  104. if (this.isRewind) pageIndex = firstPage;
  105. else if (this.isBack) pageIndex = Math.max(firstPage, currentPage - 1);
  106. else if (this.isForward) pageIndex = Math.min(lastPage, currentPage + 1);
  107. else if (this.isFastForward) pageIndex = lastPage;
  108. else {
  109. pageIndex = +options.pageIndex;
  110. pageIndex = (firstPage ? pageIndex + 1 : pageIndex);
  111. }
  112. this.pageIndex = pageIndex;
  113. if (((this.isRewind || this.isBack) && currentPage == firstPage) ||
  114. ((this.isForward || this.isFastForward) && currentPage == lastPage)) {
  115. this.$el.addClass("disabled");
  116. }
  117. else if (!(this.isRewind ||
  118. this.isBack ||
  119. this.isForward ||
  120. this.isFastForward) &&
  121. currentPage == pageIndex) {
  122. this.$el.addClass("active");
  123. }
  124. this.label = (options.label || (firstPage ? pageIndex : pageIndex + 1)) + '';
  125. var title = options.title || this.title;
  126. this.title = _.isFunction(title) ? title({label: this.label}) : title;
  127. },
  128. /**
  129. Renders a clickable anchor element under a list item.
  130. */
  131. render: function () {
  132. this.$el.empty();
  133. var anchor = document.createElement("a");
  134. anchor.href = '#';
  135. if (this.title) anchor.title = this.title;
  136. anchor.innerHTML = this.label;
  137. this.el.appendChild(anchor);
  138. this.delegateEvents();
  139. return this;
  140. },
  141. /**
  142. jQuery click event handler. Goes to the page this PageHandle instance
  143. represents. No-op if this page handle is currently active or disabled.
  144. */
  145. changePage: function (e) {
  146. e.preventDefault();
  147. var $el = this.$el;
  148. if (!$el.hasClass("active") && !$el.hasClass("disabled")) {
  149. this.collection.getPage(this.pageIndex);
  150. }
  151. return this;
  152. }
  153. });
  154. /**
  155. Paginator is a Backgrid extension that renders a series of configurable
  156. pagination handles. This extension is best used for splitting a large data
  157. set across multiple pages. If the number of pages is larger then a
  158. threshold, which is set to 10 by default, the page handles are rendered
  159. within a sliding window, plus the rewind, back, forward and fast forward
  160. control handles. The individual control handles can be turned off.
  161. @class Backgrid.Extension.Paginator
  162. */
  163. Backgrid.Extension.Paginator = Backbone.View.extend({
  164. /** @property */
  165. className: "backgrid-paginator",
  166. /** @property */
  167. windowSize: 10,
  168. /**
  169. @property {Object.<string, Object.<string, string>>} controls You can
  170. disable specific control handles by omitting certain keys.
  171. */
  172. controls: {
  173. rewind: {
  174. label: "《",
  175. title: "First"
  176. },
  177. back: {
  178. label: "〈",
  179. title: "Previous"
  180. },
  181. forward: {
  182. label: "〉",
  183. title: "Next"
  184. },
  185. fastForward: {
  186. label: "》",
  187. title: "Last"
  188. }
  189. },
  190. /**
  191. @property {Backgrid.Extension.PageHandle} pageHandle. The PageHandle
  192. class to use for rendering individual handles
  193. */
  194. pageHandle: PageHandle,
  195. /** @property */
  196. goBackFirstOnSort: true,
  197. /**
  198. Initializer.
  199. @param {Object} options
  200. @param {Backbone.Collection} options.collection
  201. @param {boolean} [options.controls]
  202. @param {boolean} [options.pageHandle=Backgrid.Extension.PageHandle]
  203. @param {boolean} [options.goBackFirstOnSort=true]
  204. */
  205. initialize: function (options) {
  206. Backgrid.requireOptions(options, ["collection"]);
  207. this.controls = options.controls || this.controls;
  208. this.pageHandle = options.pageHandle || this.pageHandle;
  209. var collection = this.collection;
  210. this.listenTo(collection, "add", this.render);
  211. this.listenTo(collection, "remove", this.render);
  212. this.listenTo(collection, "reset", this.render);
  213. if ((options.goBackFirstOnSort || this.goBackFirstOnSort) &&
  214. collection.fullCollection) {
  215. this.listenTo(collection.fullCollection, "sort", function () {
  216. collection.getFirstPage();
  217. });
  218. }
  219. },
  220. _calculateWindow: function () {
  221. var collection = this.collection;
  222. var state = collection.state;
  223. // convert all indices to 0-based here
  224. var firstPage = state.firstPage;
  225. var lastPage = +state.lastPage;
  226. lastPage = Math.max(0, firstPage ? lastPage - 1 : lastPage);
  227. var currentPage = Math.max(state.currentPage, state.firstPage);
  228. currentPage = firstPage ? currentPage - 1 : currentPage;
  229. var windowStart = Math.floor(currentPage / this.windowSize) * this.windowSize;
  230. var windowEnd = Math.min(lastPage + 1, windowStart + this.windowSize);
  231. return [windowStart, windowEnd];
  232. },
  233. /**
  234. Creates a list of page handle objects for rendering.
  235. @return {Array.<Object>} an array of page handle objects hashes
  236. */
  237. makeHandles: function () {
  238. var handles = [];
  239. var collection = this.collection;
  240. var window = this._calculateWindow();
  241. var winStart = window[0], winEnd = window[1];
  242. for (var i = winStart; i < winEnd; i++) {
  243. handles.push(new PageHandle({
  244. collection: collection,
  245. pageIndex: i
  246. }));
  247. }
  248. var controls = this.controls;
  249. _.each(["back", "rewind", "forward", "fastForward"], function (key) {
  250. var value = controls[key];
  251. if (value) {
  252. var handleCtorOpts = {
  253. collection: collection,
  254. title: value.title,
  255. label: value.label
  256. };
  257. handleCtorOpts["is" + key.slice(0, 1).toUpperCase() + key.slice(1)] = true;
  258. var handle = new PageHandle(handleCtorOpts);
  259. if (key == "rewind" || key == "back") handles.unshift(handle);
  260. else handles.push(handle);
  261. }
  262. });
  263. return handles;
  264. },
  265. /**
  266. Render the paginator handles inside an unordered list.
  267. */
  268. render: function () {
  269. this.$el.empty();
  270. if (this.handles) {
  271. for (var i = 0, l = this.handles.length; i < l; i++) {
  272. this.handles[i].remove();
  273. }
  274. }
  275. var handles = this.handles = this.makeHandles();
  276. var ul = document.createElement("ul");
  277. for (var i = 0; i < handles.length; i++) {
  278. ul.appendChild(handles[i].render().el);
  279. }
  280. this.el.appendChild(ul);
  281. return this;
  282. }
  283. });
  284. }(_, Backbone, Backgrid));