pagination.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368
  1. /*!
  2. * All material copyright ESRI, All Rights Reserved, unless otherwise specified.
  3. * See https://github.com/Esri/calcite-components/blob/master/LICENSE.md for details.
  4. * v1.0.0-beta.82
  5. */
  6. import { Component, Element, Event, h, Prop, Method, Fragment } from "@stencil/core";
  7. import { CSS, TEXT } from "./resources";
  8. const maxPagesDisplayed = 5;
  9. export class Pagination {
  10. constructor() {
  11. //--------------------------------------------------------------------------
  12. //
  13. // Public Properties
  14. //
  15. //--------------------------------------------------------------------------
  16. /** number of items per page */
  17. this.num = 20;
  18. /** index of item that should begin the page */
  19. this.start = 1;
  20. /** total number of items */
  21. this.total = 0;
  22. /** Used as an accessible label (aria-label) for the next button
  23. * @default "Next"
  24. */
  25. this.textLabelNext = TEXT.nextLabel;
  26. /** Used as an accessible label (aria-label) of the previous button
  27. * @default "Previous"
  28. */
  29. this.textLabelPrevious = TEXT.previousLabel;
  30. /** The scale of the pagination */
  31. this.scale = "m";
  32. this.previousClicked = () => {
  33. this.previousPage().then();
  34. this.emitUpdate();
  35. };
  36. this.nextClicked = () => {
  37. this.nextPage();
  38. this.emitUpdate();
  39. };
  40. }
  41. // --------------------------------------------------------------------------
  42. //
  43. // Public Methods
  44. //
  45. // --------------------------------------------------------------------------
  46. /** Go to the next page of results */
  47. async nextPage() {
  48. this.start = Math.min(this.getLastStart(), this.start + this.num);
  49. }
  50. /** Go to the previous page of results */
  51. async previousPage() {
  52. this.start = Math.max(1, this.start - this.num);
  53. }
  54. // --------------------------------------------------------------------------
  55. //
  56. // Private Methods
  57. //
  58. // --------------------------------------------------------------------------
  59. getLastStart() {
  60. const { total, num } = this;
  61. const lastStart = total % num === 0 ? total - num : Math.floor(total / num) * num;
  62. return lastStart + 1;
  63. }
  64. showLeftEllipsis() {
  65. return Math.floor(this.start / this.num) > 3;
  66. }
  67. showRightEllipsis() {
  68. return (this.total - this.start) / this.num > 3;
  69. }
  70. emitUpdate() {
  71. const changePayload = {
  72. start: this.start,
  73. total: this.total,
  74. num: this.num
  75. };
  76. this.calcitePaginationChange.emit(changePayload);
  77. this.calcitePaginationUpdate.emit(changePayload);
  78. }
  79. //--------------------------------------------------------------------------
  80. //
  81. // Render Methods
  82. //
  83. //--------------------------------------------------------------------------
  84. renderPages() {
  85. const lastStart = this.getLastStart();
  86. let end;
  87. let nextStart;
  88. // if we don't need ellipses render the whole set
  89. if (this.total / this.num <= maxPagesDisplayed) {
  90. nextStart = 1 + this.num;
  91. end = lastStart - this.num;
  92. }
  93. else {
  94. // if we're within max pages of page 1
  95. if (this.start / this.num < maxPagesDisplayed - 1) {
  96. nextStart = 1 + this.num;
  97. end = 1 + 4 * this.num;
  98. }
  99. else {
  100. // if we're within max pages of last page
  101. if (this.start + 3 * this.num >= this.total) {
  102. nextStart = lastStart - 4 * this.num;
  103. end = lastStart - this.num;
  104. }
  105. else {
  106. nextStart = this.start - this.num;
  107. end = this.start + this.num;
  108. }
  109. }
  110. }
  111. const pages = [];
  112. while (nextStart <= end) {
  113. pages.push(nextStart);
  114. nextStart = nextStart + this.num;
  115. }
  116. return pages.map((page) => this.renderPage(page));
  117. }
  118. renderPage(start) {
  119. const page = Math.floor(start / this.num) + (this.num === 1 ? 0 : 1);
  120. return (h("button", { class: {
  121. [CSS.page]: true,
  122. [CSS.selected]: start === this.start
  123. }, onClick: () => {
  124. this.start = start;
  125. this.emitUpdate();
  126. } }, page));
  127. }
  128. renderLeftEllipsis() {
  129. if (this.total / this.num > maxPagesDisplayed && this.showLeftEllipsis()) {
  130. return h("span", { class: `${CSS.ellipsis} ${CSS.ellipsisStart}` }, "\u2026");
  131. }
  132. }
  133. renderRightEllipsis() {
  134. if (this.total / this.num > maxPagesDisplayed && this.showRightEllipsis()) {
  135. return h("span", { class: `${CSS.ellipsis} ${CSS.ellipsisEnd}` }, "\u2026");
  136. }
  137. }
  138. render() {
  139. const { total, num, start } = this;
  140. const prevDisabled = num === 1 ? start <= num : start < num;
  141. const nextDisabled = num === 1 ? start + num > total : start + num > total;
  142. return (h(Fragment, null,
  143. h("button", { "aria-label": this.textLabelPrevious, class: {
  144. [CSS.previous]: true,
  145. [CSS.disabled]: prevDisabled
  146. }, disabled: prevDisabled, onClick: this.previousClicked },
  147. h("calcite-icon", { flipRtl: true, icon: "chevronLeft", scale: "s" })),
  148. total > num ? this.renderPage(1) : null,
  149. this.renderLeftEllipsis(),
  150. this.renderPages(),
  151. this.renderRightEllipsis(),
  152. this.renderPage(this.getLastStart()),
  153. h("button", { "aria-label": this.textLabelNext, class: {
  154. [CSS.next]: true,
  155. [CSS.disabled]: nextDisabled
  156. }, disabled: nextDisabled, onClick: this.nextClicked },
  157. h("calcite-icon", { flipRtl: true, icon: "chevronRight", scale: "s" }))));
  158. }
  159. static get is() { return "calcite-pagination"; }
  160. static get encapsulation() { return "shadow"; }
  161. static get originalStyleUrls() { return {
  162. "$": ["pagination.scss"]
  163. }; }
  164. static get styleUrls() { return {
  165. "$": ["pagination.css"]
  166. }; }
  167. static get properties() { return {
  168. "num": {
  169. "type": "number",
  170. "mutable": false,
  171. "complexType": {
  172. "original": "number",
  173. "resolved": "number",
  174. "references": {}
  175. },
  176. "required": false,
  177. "optional": false,
  178. "docs": {
  179. "tags": [],
  180. "text": "number of items per page"
  181. },
  182. "attribute": "num",
  183. "reflect": false,
  184. "defaultValue": "20"
  185. },
  186. "start": {
  187. "type": "number",
  188. "mutable": true,
  189. "complexType": {
  190. "original": "number",
  191. "resolved": "number",
  192. "references": {}
  193. },
  194. "required": false,
  195. "optional": false,
  196. "docs": {
  197. "tags": [],
  198. "text": "index of item that should begin the page"
  199. },
  200. "attribute": "start",
  201. "reflect": false,
  202. "defaultValue": "1"
  203. },
  204. "total": {
  205. "type": "number",
  206. "mutable": false,
  207. "complexType": {
  208. "original": "number",
  209. "resolved": "number",
  210. "references": {}
  211. },
  212. "required": false,
  213. "optional": false,
  214. "docs": {
  215. "tags": [],
  216. "text": "total number of items"
  217. },
  218. "attribute": "total",
  219. "reflect": false,
  220. "defaultValue": "0"
  221. },
  222. "textLabelNext": {
  223. "type": "string",
  224. "mutable": false,
  225. "complexType": {
  226. "original": "string",
  227. "resolved": "string",
  228. "references": {}
  229. },
  230. "required": false,
  231. "optional": false,
  232. "docs": {
  233. "tags": [{
  234. "name": "default",
  235. "text": "\"Next\""
  236. }],
  237. "text": "Used as an accessible label (aria-label) for the next button"
  238. },
  239. "attribute": "text-label-next",
  240. "reflect": false,
  241. "defaultValue": "TEXT.nextLabel"
  242. },
  243. "textLabelPrevious": {
  244. "type": "string",
  245. "mutable": false,
  246. "complexType": {
  247. "original": "string",
  248. "resolved": "string",
  249. "references": {}
  250. },
  251. "required": false,
  252. "optional": false,
  253. "docs": {
  254. "tags": [{
  255. "name": "default",
  256. "text": "\"Previous\""
  257. }],
  258. "text": "Used as an accessible label (aria-label) of the previous button"
  259. },
  260. "attribute": "text-label-previous",
  261. "reflect": false,
  262. "defaultValue": "TEXT.previousLabel"
  263. },
  264. "scale": {
  265. "type": "string",
  266. "mutable": false,
  267. "complexType": {
  268. "original": "Scale",
  269. "resolved": "\"l\" | \"m\" | \"s\"",
  270. "references": {
  271. "Scale": {
  272. "location": "import",
  273. "path": "../interfaces"
  274. }
  275. }
  276. },
  277. "required": false,
  278. "optional": false,
  279. "docs": {
  280. "tags": [],
  281. "text": "The scale of the pagination"
  282. },
  283. "attribute": "scale",
  284. "reflect": true,
  285. "defaultValue": "\"m\""
  286. }
  287. }; }
  288. static get events() { return [{
  289. "method": "calcitePaginationUpdate",
  290. "name": "calcitePaginationUpdate",
  291. "bubbles": true,
  292. "cancelable": true,
  293. "composed": true,
  294. "docs": {
  295. "tags": [{
  296. "name": "deprecated",
  297. "text": "use calcitePaginationChange instead"
  298. }],
  299. "text": "Emitted whenever the selected page changes."
  300. },
  301. "complexType": {
  302. "original": "PaginationDetail",
  303. "resolved": "PaginationDetail",
  304. "references": {
  305. "PaginationDetail": {
  306. "location": "local"
  307. }
  308. }
  309. }
  310. }, {
  311. "method": "calcitePaginationChange",
  312. "name": "calcitePaginationChange",
  313. "bubbles": true,
  314. "cancelable": true,
  315. "composed": true,
  316. "docs": {
  317. "tags": [{
  318. "name": "see",
  319. "text": "[PaginationDetail](https://github.com/Esri/calcite-components/blob/master/src/components/pagination/calcite-pagination.tsx#L18)"
  320. }],
  321. "text": "Emitted whenever the selected page changes."
  322. },
  323. "complexType": {
  324. "original": "PaginationDetail",
  325. "resolved": "PaginationDetail",
  326. "references": {
  327. "PaginationDetail": {
  328. "location": "local"
  329. }
  330. }
  331. }
  332. }]; }
  333. static get methods() { return {
  334. "nextPage": {
  335. "complexType": {
  336. "signature": "() => Promise<void>",
  337. "parameters": [],
  338. "references": {
  339. "Promise": {
  340. "location": "global"
  341. }
  342. },
  343. "return": "Promise<void>"
  344. },
  345. "docs": {
  346. "text": "Go to the next page of results",
  347. "tags": []
  348. }
  349. },
  350. "previousPage": {
  351. "complexType": {
  352. "signature": "() => Promise<void>",
  353. "parameters": [],
  354. "references": {
  355. "Promise": {
  356. "location": "global"
  357. }
  358. },
  359. "return": "Promise<void>"
  360. },
  361. "docs": {
  362. "text": "Go to the previous page of results",
  363. "tags": []
  364. }
  365. }
  366. }; }
  367. static get elementRef() { return "el"; }
  368. }