/src/js/cilantro/models/paginator.js

https://github.com/cbmi/cilantro · JavaScript · 198 lines · 134 code · 43 blank · 21 comment · 18 complexity · 89e3e3fbbb074bdac25459fc0fb959b1 MD5 · raw file

  1. /* global define */
  2. define([
  3. 'underscore',
  4. 'backbone',
  5. '../core'
  6. ], function(_, Backbone, c) {
  7. var PaginatorMixin = {
  8. comparator: 'page_num',
  9. refresh: function() {
  10. var url = _.result(this, 'url');
  11. if (this.pending) {
  12. // Request already being made, otherwise abort
  13. if (url === this.pending.url) return;
  14. if (this.pending.abort) {
  15. this.pending.abort();
  16. }
  17. }
  18. var _this = this;
  19. this.pending = this.fetch({
  20. reset: true
  21. }).done(function() {
  22. delete _this.pending;
  23. _this.setCurrentPage(_this.models[0].id);
  24. });
  25. // This is a deferred and does not contain any of the
  26. // ajax settings, so we set the url for later reference.
  27. this.pending.url = url;
  28. },
  29. // Parses the initial fetch which is a single page, resets if necessary
  30. parse: function(resp, options) {
  31. if (!options.reset) {
  32. // TODO Smartly shuffle pages when only the size changes.
  33. // The data is not invalid, just broken up differently.
  34. this.reset(null, {
  35. silent: true
  36. });
  37. }
  38. if (resp) {
  39. this.perPage = resp.per_page; // jshint ignore:line
  40. this.trigger('change:pagesize', this, this.perPage);
  41. this.numPages = resp.num_pages; // jshint ignore:line
  42. this.trigger('change:pagecount', this, this.numPages);
  43. this.objectCount = resp.item_count; // jshint ignore:line
  44. this.trigger('change:objectcount', this, this.objectCount);
  45. this.currentPageNum = null;
  46. this.setCurrentPage(resp.page_num); // jshint ignore:line
  47. return [resp];
  48. }
  49. },
  50. // Ensures `num` is within the bounds
  51. hasPage: function(num) {
  52. return (1 <= num && num <= this.numPages);
  53. },
  54. // Set the current page which triggers the 'change:page' event
  55. setCurrentPage: function(num) {
  56. if (num === this.currentPageNum) return;
  57. if (!this.hasPage(num)) {
  58. throw new Error('Cannot set the current page out of bounds');
  59. }
  60. this.previousPageNum = this.currentPageNum;
  61. this.currentPageNum = num;
  62. return this.trigger.apply(this, ['change:currentpage', this].concat(
  63. [].slice.call(this.getCurrentPageStats())));
  64. },
  65. // Gets or fetches the page for num, if options.active is true
  66. // the page is set as the current one.
  67. // If the page does not already exist, the model is created, added
  68. // to the collected and fetched. Once fetched, the page is resolved.
  69. getPage: function(num, options) {
  70. if (!options) options = {};
  71. if (!this.hasPage(num)) return;
  72. var model = this.get(num);
  73. if (!model && options.load !== false) {
  74. model = new this.model({
  75. page_num: num // jshint ignore:line
  76. });
  77. model.pending = true;
  78. this.add(model);
  79. model.fetch().done(function() {
  80. delete model.pending;
  81. });
  82. }
  83. if (model && options.active !== false) {
  84. this.setCurrentPage(num);
  85. }
  86. return model;
  87. },
  88. getCurrentPage: function(options) {
  89. return this.getPage(this.currentPageNum, options);
  90. },
  91. getFirstPage: function(options) {
  92. return this.getPage(1, options);
  93. },
  94. getLastPage: function(options) {
  95. return this.getPage(this.numPages, options);
  96. },
  97. // Gets the next page relative to the current page
  98. getNextPage: function(num, options) {
  99. if (!num) num = this.currentPageNum;
  100. return this.getPage(num + 1, options);
  101. },
  102. // Gets the previous page relative to the current page
  103. getPreviousPage: function(num, options) {
  104. if (!num) num = this.currentPageNum;
  105. return this.getPage(num - 1, options);
  106. },
  107. // Checks if the current page is pending. Use of this check prevents
  108. // stampeding the server with requests if the current one has not
  109. // responded yet.
  110. pageIsLoading: function(num) {
  111. if (!num) num = this.currentPageNum;
  112. var page = this.getPage(num, {
  113. active: false,
  114. load: false
  115. });
  116. if (page) {
  117. return !!page.pending;
  118. }
  119. },
  120. getPageCount: function() {
  121. return this.numPages;
  122. },
  123. getCurrentPageStats: function() {
  124. return [
  125. this.currentPageNum, {
  126. previous: this.previousPageNum,
  127. first: this.currentPageNum === 1,
  128. last: this.currentPageNum === this.numPages
  129. }
  130. ];
  131. }
  132. };
  133. // Provides the facility for fetching it's own clice of content based on
  134. // the collection it's contained in.
  135. var Page = Backbone.Model.extend({
  136. idAttribute: 'page_num',
  137. url: function() {
  138. var url = _.result(this.collection, 'url');
  139. return c.utils.alterUrlParams(url, {
  140. page: this.id,
  141. per_page: this.collection.perPage // jshint ignore:line
  142. });
  143. }
  144. });
  145. // Paginator collection for managing its pages
  146. var Paginator = Backbone.Collection.extend({
  147. model: Page
  148. });
  149. _.extend(Paginator.prototype, PaginatorMixin);
  150. return {
  151. PaginatorMixin: PaginatorMixin,
  152. Page: Page,
  153. Paginator: Paginator
  154. };
  155. });