Blame Raw
· · · 1256 lines (39.5 KB)
0 contributors
1/*
2 * pagination.js 2.6.0
3 * A jQuery plugin to provide simple yet fully customisable pagination.
4 * https://github.com/superRaytin/paginationjs
5 *
6 * Homepage: http://pagination.js.org
7 *
8 * Copyright 2014-2100, superRaytin
9 * Released under the MIT license.
10 */
11
12(function(global, $) {
13
14 if (typeof $ === 'undefined') {
15 throwError('Pagination requires jQuery.');
16 }
17
18 var pluginName = 'pagination';
19
20 var pluginHookMethod = 'addHook';
21
22 var eventPrefix = '__pagination-';
23
24 if ($.fn.pagination) {
25 throwError('plugin conflicted, the name "pagination" has been taken by another jQuery plugin.');
26 }
27
28 $.fn[pluginName] = function(options) {
29
30 if (typeof options === 'undefined') {
31 return this;
32 }
33
34 var container = $(this);
35
36 var attributes = $.extend({}, $.fn[pluginName].defaults, options);
37
38 var pagination = {
39
40 initialize: function() {
41 var self = this;
42
43 // Cache data for current instance
44 if (!container.data('pagination')) {
45 container.data('pagination', {});
46 }
47
48 if (self.callHook('beforeInit') === false) return;
49
50 // Pagination has been initialized, destroy it
51 if (container.data('pagination').initialized) {
52 $('.paginationjs', container).remove();
53 }
54
55 // Whether to disable Pagination at the initialization
56 self.disabled = !!attributes.disabled;
57
58 // Model will be passed to the callback function
59 var model = self.model = {
60 pageRange: attributes.pageRange,
61 pageSize: attributes.pageSize
62 };
63
64 // Parse dataSource to find available paging data
65 self.parseDataSource(attributes.dataSource, function(dataSource) {
66
67 // Asynchronous mode
68 self.isAsync = Helpers.isString(dataSource);
69 if (Helpers.isArray(dataSource)) {
70 model.totalNumber = attributes.totalNumber = dataSource.length;
71 }
72
73 // Asynchronous mode and a 'totalNumberLocator' has been specified
74 self.isDynamicTotalNumber = self.isAsync && attributes.totalNumberLocator;
75
76 var el = self.render(true);
77
78 // Add extra className to the pagination element
79 if (attributes.className) {
80 el.addClass(attributes.className);
81 }
82
83 model.el = el;
84
85 // Append / prepend pagination element to the container
86 container[attributes.position === 'bottom' ? 'append' : 'prepend'](el);
87
88 // Bind events
89 self.observer();
90
91 // Mark pagination has been initialized
92 container.data('pagination').initialized = true;
93
94 // Call hook after initialization
95 self.callHook('afterInit', el);
96 });
97 },
98
99 render: function(isBoot) {
100 var self = this;
101 var model = self.model;
102 var el = model.el || $('<div class="paginationjs"></div>');
103 var isForced = isBoot !== true;
104
105 self.callHook('beforeRender', isForced);
106
107 var currentPage = model.pageNumber || attributes.pageNumber;
108 var pageRange = attributes.pageRange || 0;
109 var totalPage = self.getTotalPage();
110
111 var rangeStart = currentPage - pageRange;
112 var rangeEnd = currentPage + pageRange;
113
114 if (rangeEnd > totalPage) {
115 rangeEnd = totalPage;
116 rangeStart = totalPage - pageRange * 2;
117 rangeStart = rangeStart < 1 ? 1 : rangeStart;
118 }
119
120 if (rangeStart <= 1) {
121 rangeStart = 1;
122 rangeEnd = Math.min(pageRange * 2 + 1, totalPage);
123 }
124
125 el.html(self.generateHTML({
126 currentPage: currentPage,
127 pageRange: pageRange,
128 rangeStart: rangeStart,
129 rangeEnd: rangeEnd
130 }));
131
132 // Whether to hide pagination when there is only one page
133 if (attributes.hideOnlyOnePage) {
134 el[totalPage <= 1 ? 'hide' : 'show']();
135 }
136
137 self.callHook('afterRender', isForced);
138
139 return el;
140 },
141
142 getPageLinkTag: function(text) {
143 var pageLink = attributes.pageLink;
144 return pageLink ? `<a href="${pageLink}">${text}</a>` : `<a>${text}</a>`;
145 },
146
147 // Generate HTML for page numbers
148 generatePageNumbersHTML: function(args) {
149 var self = this;
150 var currentPage = args.currentPage;
151 var totalPage = self.getTotalPage();
152 var getPageLinkTag = self.getPageLinkTag;
153 var rangeStart = args.rangeStart;
154 var rangeEnd = args.rangeEnd;
155 var html = '';
156 var i;
157
158 var ellipsisText = attributes.ellipsisText;
159
160 var classPrefix = attributes.classPrefix;
161 var pageClassName = attributes.pageClassName || '';
162 var activeClassName = attributes.activeClassName || '';
163 var disableClassName = attributes.disableClassName || '';
164
165 // Display all page numbers if page range disabled
166 if (attributes.pageRange === null) {
167 for (i = 1; i <= totalPage; i++) {
168 if (i == currentPage) {
169 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
170 } else {
171 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
172 }
173 }
174 return html;
175 }
176
177 if (rangeStart <= 3) {
178 for (i = 1; i < rangeStart; i++) {
179 if (i == currentPage) {
180 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
181 } else {
182 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
183 }
184 }
185 } else {
186 if (!attributes.hideFirstOnEllipsisShow) {
187 html += `<li class="${classPrefix}-page ${classPrefix}-first J-paginationjs-page ${pageClassName}" data-num="1">${getPageLinkTag(1)}</li>`;
188 }
189 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`;
190 }
191
192 for (i = rangeStart; i <= rangeEnd; i++) {
193 if (i == currentPage) {
194 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName} ${activeClassName}" data-num="${i}"><a>${i}</a></li>`;
195 } else {
196 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
197 }
198 }
199
200 if (rangeEnd >= totalPage - 2) {
201 for (i = rangeEnd + 1; i <= totalPage; i++) {
202 html += `<li class="${classPrefix}-page J-paginationjs-page ${pageClassName}" data-num="${i}">${getPageLinkTag(i)}</li>`;
203 }
204 } else {
205 html += `<li class="${classPrefix}-ellipsis ${disableClassName}"><a>${ellipsisText}</a></li>`;
206
207 if (!attributes.hideLastOnEllipsisShow) {
208 html += `<li class="${classPrefix}-page ${classPrefix}-last J-paginationjs-page ${pageClassName}" data-num="${totalPage}">${getPageLinkTag(totalPage)}</li>`;
209 }
210 }
211
212 return html;
213 },
214
215 // Generate HTML content
216 generateHTML: function(args) {
217 var self = this;
218 var currentPage = args.currentPage;
219 var totalPage = self.getTotalPage();
220 var getPageLinkTag = self.getPageLinkTag;
221
222 var totalNumber = self.getTotalNumber();
223
224 var pageSize = attributes.pageSize;
225 var showPrevious = attributes.showPrevious;
226 var showNext = attributes.showNext;
227 var showPageNumbers = attributes.showPageNumbers;
228 var showNavigator = attributes.showNavigator;
229 var showSizeChanger = attributes.showSizeChanger;
230 var sizeChangerOptions = attributes.sizeChangerOptions;
231 var showGoInput = attributes.showGoInput;
232 var showGoButton = attributes.showGoButton;
233
234 var prevText = attributes.prevText;
235 var nextText = attributes.nextText;
236 var firstText = attributes.firstText;
237 var lastText = attributes.lastText;
238 var goButtonText = attributes.goButtonText;
239
240 var classPrefix = attributes.classPrefix;
241 var disableClassName = attributes.disableClassName || '';
242 var ulClassName = attributes.ulClassName || '';
243 var prevClassName = attributes.prevClassName || '';
244 var nextClassName = attributes.nextClassName || '';
245 var firstClassName = attributes.firstClassName || '';
246 var lastClassName = attributes.lastClassName || '';
247
248 var html = '';
249 var sizeSelect = `<select class="J-paginationjs-size-select">`;
250 var goInput = '<input type="text" class="J-paginationjs-go-pagenumber">';
251 var goButton = `<input type="button" class="J-paginationjs-go-button" value="${goButtonText}">`;
252 var formattedString;
253
254 var formatSizeChanger = typeof attributes.formatSizeChanger === 'function' ? attributes.formatSizeChanger(currentPage, totalPage, totalNumber) : attributes.formatSizeChanger;
255 var formatNavigator = typeof attributes.formatNavigator === 'function' ? attributes.formatNavigator(currentPage, totalPage, totalNumber) : attributes.formatNavigator;
256 var formatGoInput = typeof attributes.formatGoInput === 'function' ? attributes.formatGoInput(goInput, currentPage, totalPage, totalNumber) : attributes.formatGoInput;
257 var formatGoButton = typeof attributes.formatGoButton === 'function' ? attributes.formatGoButton(goButton, currentPage, totalPage, totalNumber) : attributes.formatGoButton;
258
259 var autoHidePrevious = typeof attributes.autoHidePrevious === 'function' ? attributes.autoHidePrevious() : attributes.autoHidePrevious;
260 var autoHideNext = typeof attributes.autoHideNext === 'function' ? attributes.autoHideNext() : attributes.autoHideNext;
261
262 var header = typeof attributes.header === 'function' ? attributes.header(currentPage, totalPage, totalNumber) : attributes.header;
263 var footer = typeof attributes.footer === 'function' ? attributes.footer(currentPage, totalPage, totalNumber) : attributes.footer;
264
265 // Prepend extra contents to the pagination buttons
266 if (header) {
267 formattedString = self.replaceVariables(header, {
268 currentPage: currentPage,
269 totalPage: totalPage,
270 totalNumber: totalNumber
271 });
272 html += formattedString;
273 }
274
275 // Whether to display navigator
276 if (showNavigator) {
277 if (formatNavigator) {
278 formattedString = self.replaceVariables(formatNavigator, {
279 currentPage: currentPage,
280 totalPage: totalPage,
281 totalNumber: totalNumber,
282 rangeStart: (currentPage - 1) * pageSize + 1,
283 rangeEnd: Math.min(currentPage * pageSize, totalNumber)
284 });
285 html += `<div class="${classPrefix}-nav J-paginationjs-nav">${formattedString}</div>`;
286 }
287 }
288
289 if (showPrevious || showPageNumbers || showNext) {
290 html += '<div class="paginationjs-pages">';
291
292 if (ulClassName) {
293 html += `<ul class="${ulClassName}">`;
294 } else {
295 html += '<ul>';
296 }
297
298 // Whether to display Previous button
299 if (showPrevious) {
300 if (currentPage <= 1) {
301 if (!autoHidePrevious) {
302 html += `<li class="${classPrefix}-first ${disableClassName} ${firstClassName}"><a>${firstText}</a></li>`;
303 html += `<li class="${classPrefix}-prev ${disableClassName} ${prevClassName}"><a>${prevText}</a></li>`;
304 }
305 } else {
306 html += `<li class="${classPrefix}-first J-paginationjs-first ${firstClassName}" data-num="1" title="First page">${getPageLinkTag(firstText)}</li>`;
307 html += `<li class="${classPrefix}-prev J-paginationjs-previous ${prevClassName}" data-num="${currentPage - 1}" title="Previous page">${getPageLinkTag(prevText)}</li>`;
308 }
309 }
310
311 // Whether to display page numbers
312 if (showPageNumbers) {
313 html += self.generatePageNumbersHTML(args);
314 }
315
316 // Whether to display Next button
317 if (showNext) {
318 if (currentPage >= totalPage) {
319 if (!autoHideNext) {
320 html += `<li class="${classPrefix}-next ${disableClassName} ${nextClassName}"><a>${nextText}</a></li>`;
321 html += `<li class="${classPrefix}-last ${disableClassName} ${lastClassName}"><a>${lastText}</a></li>`;
322 }
323 } else {
324 html += `<li class="${classPrefix}-next J-paginationjs-next ${nextClassName}" data-num="${currentPage + 1}" title="Next page">${getPageLinkTag(nextText)}</li>`;
325 html += `<li class="${classPrefix}-last J-paginationjs-last ${lastClassName}" data-num="${totalPage}" title="Last page">${getPageLinkTag(lastText)}</li>`;
326 }
327 }
328 html += `</ul></div>`;
329 }
330
331 if (showSizeChanger) {
332 if (Helpers.isArray(sizeChangerOptions)) {
333 if (sizeChangerOptions.indexOf(pageSize) === -1) {
334 sizeChangerOptions.unshift(pageSize);
335 sizeChangerOptions.sort((a, b) => a - b);
336 }
337 for (let i = 0; i < sizeChangerOptions.length; i++) {
338 sizeSelect += `<option value="${sizeChangerOptions[i]}"${(sizeChangerOptions[i] === pageSize ? ' selected' : '')}>${sizeChangerOptions[i]} / page</option>`;
339 }
340 sizeSelect += `</select>`;
341 formattedString = sizeSelect;
342
343 if (formatSizeChanger) {
344 formattedString = self.replaceVariables(formatSizeChanger, {
345 length: sizeSelect,
346 total: totalNumber
347 });
348 }
349 html += `<div class="paginationjs-size-changer">${formattedString}</div>`;
350 }
351 }
352
353 // Whether to display Go input
354 if (showGoInput) {
355 if (formatGoInput) {
356 formattedString = self.replaceVariables(formatGoInput, {
357 currentPage: currentPage,
358 totalPage: totalPage,
359 totalNumber: totalNumber,
360 input: goInput
361 });
362 html += `<div class="${classPrefix}-go-input">${formattedString}</div>`;
363 }
364 }
365
366 // Whether to display Go button
367 if (showGoButton) {
368 if (formatGoButton) {
369 formattedString = self.replaceVariables(formatGoButton, {
370 currentPage: currentPage,
371 totalPage: totalPage,
372 totalNumber: totalNumber,
373 button: goButton
374 });
375 html += `<div class="${classPrefix}-go-button">${formattedString}</div>`;
376 }
377 }
378
379 // Append extra contents to the pagination buttons
380 if (footer) {
381 formattedString = self.replaceVariables(footer, {
382 currentPage: currentPage,
383 totalPage: totalPage,
384 totalNumber: totalNumber
385 });
386 html += formattedString;
387 }
388
389 return html;
390 },
391
392 // dataSource is a request URL and a 'totalNumberLocator' function specified
393 // execute it to find out 'totalNumber' from the response
394 findTotalNumberFromRemoteResponse: function(response) {
395 var self = this;
396 self.model.totalNumber = attributes.totalNumberLocator(response);
397 },
398
399 // Go to the specified page
400 go: function(number, callback) {
401 var self = this;
402 var model = self.model;
403
404 if (self.disabled) return;
405
406 var pageNumber = number;
407 pageNumber = parseInt(pageNumber);
408
409 if (!pageNumber || pageNumber < 1) return;
410
411 var pageSize = attributes.pageSize;
412 var totalNumber = self.getTotalNumber();
413 var totalPage = self.getTotalPage();
414
415 if (totalNumber > 0 && pageNumber > totalPage) return;
416
417 // Pick paging data in synchronous mode
418 if (!self.isAsync) {
419 render(self.getPagingData(pageNumber));
420 return;
421 }
422
423 var postData = {};
424 var alias = attributes.alias || {};
425 var pageSizeName = alias.pageSize ? alias.pageSize : 'pageSize';
426 var pageNumberName = alias.pageNumber ? alias.pageNumber : 'pageNumber';
427 postData[pageSizeName] = pageSize;
428 postData[pageNumberName] = pageNumber;
429
430 var ajaxParams = typeof attributes.ajax === 'function' ? attributes.ajax() : attributes.ajax;
431
432 // If the pageNumber's value starts with 0 via Ajax
433 if (ajaxParams && ajaxParams.pageNumberStartWithZero) {
434 postData[pageNumberName] = pageNumber - 1;
435 }
436
437 var formatAjaxParams = {
438 type: 'get',
439 cache: false,
440 data: {},
441 contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
442 dataType: 'json',
443 async: true
444 };
445
446 $.extend(true, formatAjaxParams, ajaxParams);
447 $.extend(formatAjaxParams.data, postData);
448
449 formatAjaxParams.url = attributes.dataSource;
450 formatAjaxParams.success = function(response) {
451 try {
452 self.model.originalResponse = response;
453 if (self.isDynamicTotalNumber) {
454 self.findTotalNumberFromRemoteResponse(response);
455 } else {
456 self.model.totalNumber = attributes.totalNumber;
457 }
458
459 var finalData = self.filterDataWithLocator(response);
460 render(finalData);
461 } catch (e) {
462 if(typeof attributes.onError === 'function') {
463 attributes.onError(e, 'ajaxSuccessHandlerError');
464 } else {
465 throw e;
466 }
467 }
468 };
469 formatAjaxParams.error = function(jqXHR, textStatus, errorThrown) {
470 attributes.formatAjaxError && attributes.formatAjaxError(jqXHR, textStatus, errorThrown);
471 self.enable();
472 };
473
474 self.disable();
475
476 if (attributes.ajaxFunction) {
477 attributes.ajaxFunction(formatAjaxParams);
478 } else {
479 $.ajax(formatAjaxParams);
480 }
481
482 function render(data) {
483 if (self.callHook('beforePaging', pageNumber) === false) return false;
484
485 // Pagination direction
486 model.direction = typeof model.pageNumber === 'undefined' ? 0 : (pageNumber > model.pageNumber ? 1 : -1);
487
488 model.pageNumber = pageNumber;
489
490 self.render();
491
492 if (self.disabled && self.isAsync) {
493 // enable pagination
494 self.enable();
495 }
496
497 // cache model data
498 container.data('pagination').model = model;
499
500 // format result data before callback invoked
501 if (attributes.formatResult) {
502 var cloneData = $.extend(true, [], data);
503 if (!Helpers.isArray(data = attributes.formatResult(cloneData))) {
504 data = cloneData;
505 }
506 }
507
508 container.data('pagination').currentPageData = data;
509
510 self.doCallback(data, callback);
511
512 self.callHook('afterPaging', pageNumber);
513
514 if (pageNumber == 1) {
515 self.callHook('afterIsFirstPage');
516 } else if (pageNumber == self.getTotalPage()) {
517 self.callHook('afterIsLastPage');
518 }
519 }
520 },
521
522 doCallback: function(data, customCallback) {
523 var self = this;
524 var model = self.model;
525
526 if (typeof customCallback === 'function') {
527 customCallback(data, model);
528 } else if (typeof attributes.callback === 'function') {
529 attributes.callback(data, model);
530 }
531 },
532
533 destroy: function() {
534 if (this.callHook('beforeDestroy') === false) return;
535
536 this.model.el.remove();
537 container.off();
538
539 // Remove style element
540 $('#paginationjs-style').remove();
541
542 this.callHook('afterDestroy');
543 },
544
545 previous: function(callback) {
546 this.go(this.model.pageNumber - 1, callback);
547 },
548
549 next: function(callback) {
550 this.go(this.model.pageNumber + 1, callback);
551 },
552
553 first: function(callback) {
554 this.go(1, callback);
555 },
556
557 last: function(callback) {
558 this.go(this.model.totalPage, callback);
559 },
560
561 disable: function() {
562 var self = this;
563 var source = self.isAsync ? 'async' : 'sync';
564
565 if (self.callHook('beforeDisable', source) === false) return;
566
567 self.disabled = true;
568 self.model.disabled = true;
569
570 self.callHook('afterDisable', source);
571 },
572
573 enable: function() {
574 var self = this;
575 var source = self.isAsync ? 'async' : 'sync';
576
577 if (self.callHook('beforeEnable', source) === false) return;
578
579 self.disabled = false;
580 self.model.disabled = false;
581
582 self.callHook('afterEnable', source);
583 },
584
585 refresh: function(callback) {
586 this.go(this.model.pageNumber, callback);
587 },
588
589 show: function() {
590 var self = this;
591
592 if (self.model.el.is(':visible')) return;
593
594 self.model.el.show();
595 },
596
597 hide: function() {
598 var self = this;
599
600 if (!self.model.el.is(':visible')) return;
601
602 self.model.el.hide();
603 },
604
605 // Replace variables for template string
606 replaceVariables: function(template, variables) {
607 var formattedString;
608
609 for (var key in variables) {
610 var value = variables[key];
611 var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img');
612
613 formattedString = (formattedString || template).replace(regexp, value);
614 }
615
616 return formattedString;
617 },
618
619 getPagingData: function(number) {
620 var pageSize = attributes.pageSize;
621 var dataSource = attributes.dataSource;
622 var totalNumber = this.getTotalNumber();
623
624 var start = pageSize * (number - 1) + 1;
625 var end = Math.min(number * pageSize, totalNumber);
626
627 return dataSource.slice(start - 1, end);
628 },
629
630 getTotalNumber: function() {
631 return this.model.totalNumber || attributes.totalNumber || 0;
632 },
633
634 getTotalPage: function() {
635 return Math.ceil(this.getTotalNumber() / attributes.pageSize);
636 },
637
638 getLocator: function(locator) {
639 var result;
640
641 if (typeof locator === 'string') {
642 result = locator;
643 } else if (typeof locator === 'function') {
644 result = locator();
645 } else {
646 throwError('"locator" is incorrect. Expect string or function type.');
647 }
648
649 return result;
650 },
651
652 // Filter data with "locator"
653 filterDataWithLocator: function(dataSource) {
654 var locator = this.getLocator(attributes.locator);
655 var filteredData;
656
657 // Datasource is an Object, use "locator" to locate available data
658 if (Helpers.isObject(dataSource)) {
659 try {
660 $.each(locator.split('.'), function(index, item) {
661 filteredData = (filteredData ? filteredData : dataSource)[item];
662 });
663 }
664 catch (e) {
665 // ignore
666 }
667
668 if (!filteredData) {
669 throwError('dataSource.' + locator + ' is undefined.');
670 } else if (!Helpers.isArray(filteredData)) {
671 throwError('dataSource.' + locator + ' should be an Array.');
672 }
673 }
674
675 return filteredData || dataSource;
676 },
677
678 parseDataSource: function(dataSource, callback) {
679 var self = this;
680
681 if (Helpers.isObject(dataSource)) {
682 callback(attributes.dataSource = self.filterDataWithLocator(dataSource));
683 } else if (Helpers.isArray(dataSource)) {
684 callback(attributes.dataSource = dataSource);
685 } else if (typeof dataSource === 'function') {
686 attributes.dataSource(function(data) {
687 if (!Helpers.isArray(data)) {
688 throwError('The parameter of "done" Function should be an Array.');
689 }
690 self.parseDataSource.call(self, data, callback);
691 });
692 } else if (typeof dataSource === 'string') {
693 if (/^https?|file:/.test(dataSource)) {
694 attributes.ajaxDataType = 'jsonp';
695 }
696 callback(dataSource);
697 } else {
698 throwError('Unexpected dataSource type');
699 }
700 },
701
702 callHook: function(hook) {
703 var paginationData = container.data('pagination') || {};
704 var result;
705
706 var args = Array.prototype.slice.apply(arguments);
707 args.shift();
708
709 if (attributes[hook] && typeof attributes[hook] === 'function') {
710 if (attributes[hook].apply(global, args) === false) {
711 result = false;
712 }
713 }
714
715 if (paginationData.hooks && paginationData.hooks[hook]) {
716 $.each(paginationData.hooks[hook], function(index, item) {
717 if (item.apply(global, args) === false) {
718 result = false;
719 }
720 });
721 }
722
723 return result !== false;
724 },
725
726 observer: function() {
727 var self = this;
728 var el = self.model.el;
729
730 // Go to specified page number
731 container.on(eventPrefix + 'go', function(event, pageNumber, done) {
732 if (typeof pageNumber === 'string') {
733 pageNumber = parseInt(pageNumber.trim());
734 }
735
736 if (!pageNumber) return;
737
738 if (typeof pageNumber !== 'number') {
739 throwError('"pageNumber" is incorrect. (Number)');
740 }
741
742 self.go(pageNumber, done);
743 });
744
745 // Page number button click listener
746 el.on('click', '.J-paginationjs-page', function(event) {
747 var current = $(event.currentTarget);
748 var pageNumber = current.attr('data-num').trim();
749
750 if (!pageNumber || current.hasClass(attributes.disableClassName) || current.hasClass(attributes.activeClassName)) return;
751
752 if (self.callHook('beforePageOnClick', event, pageNumber) === false) return false;
753
754 self.go(pageNumber);
755
756 self.callHook('afterPageOnClick', event, pageNumber);
757
758 if (!attributes.pageLink) return false;
759 });
760
761 // Previous button click listener
762 el.on('click', '.J-paginationjs-previous', function(event) {
763 var current = $(event.currentTarget);
764 var pageNumber = current.attr('data-num').trim();
765
766 if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
767
768 if (self.callHook('beforePreviousOnClick', event, pageNumber) === false) return false;
769
770 self.go(pageNumber);
771
772 self.callHook('afterPreviousOnClick', event, pageNumber);
773
774 if (!attributes.pageLink) return false;
775 });
776
777 // Next button click listener
778 el.on('click', '.J-paginationjs-next', function(event) {
779 var current = $(event.currentTarget);
780 var pageNumber = current.attr('data-num').trim();
781
782 if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
783
784 if (self.callHook('beforeNextOnClick', event, pageNumber) === false) return false;
785
786 self.go(pageNumber);
787
788 self.callHook('afterNextOnClick', event, pageNumber);
789
790 if (!attributes.pageLink) return false;
791 });
792
793 // First button click listener
794 el.on('click', '.J-paginationjs-first', function(event) {
795 var current = $(event.currentTarget);
796 var pageNumber = current.attr('data-num').trim();
797
798 if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
799
800 if (self.callHook('beforeFirstOnClick', event, pageNumber) === false) return false;
801
802 self.go(pageNumber);
803
804 self.callHook('afterFirstOnClick', event, pageNumber);
805
806 if (!attributes.pageLink) return false;
807 });
808
809 // Last button click listener
810 el.on('click', '.J-paginationjs-last', function(event) {
811 var current = $(event.currentTarget);
812 var pageNumber = current.attr('data-num').trim();
813
814 if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
815
816 if (self.callHook('beforeLastOnClick', event, pageNumber) === false) return false;
817
818 self.go(pageNumber);
819
820 self.callHook('afterLastOnClick', event, pageNumber);
821
822 if (!attributes.pageLink) return false;
823 });
824
825 // Go button click listener
826 el.on('click', '.J-paginationjs-go-button', function(event) {
827 var pageNumber = $('.J-paginationjs-go-pagenumber', el).val();
828
829 if (self.callHook('beforeGoButtonOnClick', event, pageNumber) === false) return false;
830
831 container.trigger(eventPrefix + 'go', pageNumber);
832
833 self.callHook('afterGoButtonOnClick', event, pageNumber);
834 });
835
836 // go input enter keyup listener
837 el.on('keyup', '.J-paginationjs-go-pagenumber', function(event) {
838 if (event.which === 13) {
839 var pageNumber = $(event.currentTarget).val();
840
841 if (self.callHook('beforeGoInputOnEnter', event, pageNumber) === false) return false;
842
843 container.trigger(eventPrefix + 'go', pageNumber);
844
845 // Maintain the cursor
846 $('.J-paginationjs-go-pagenumber', el).focus();
847
848 self.callHook('afterGoInputOnEnter', event, pageNumber);
849 }
850 });
851
852 el.on('change', '.J-paginationjs-size-select', function(event) {
853 var current = $(event.currentTarget);
854 var size = parseInt(current.val());
855 var currentPage = self.model.pageNumber || attributes.pageNumber;
856
857 if (typeof size !== 'number') return;
858
859 if (self.callHook('beforeSizeSelectorChange', event, size) === false) return false;
860
861 attributes.pageSize = size;
862 self.model.pageSize = size;
863 self.model.totalPage = self.getTotalPage();
864 if (currentPage > self.model.totalPage) {
865 currentPage = self.model.totalPage;
866 }
867 self.go(currentPage);
868
869 self.callHook('afterSizeSelectorChange', event, size);
870
871 if (!attributes.pageLink) return false;
872 });
873
874 // Previous page
875 container.on(eventPrefix + 'previous', function(event, done) {
876 self.previous(done);
877 });
878
879 // Next page
880 container.on(eventPrefix + 'next', function(event, done) {
881 self.next(done);
882 });
883
884 // First page
885 container.on(eventPrefix + 'first', function(event, done) {
886 self.first(done);
887 });
888
889 // Last page
890 container.on(eventPrefix + 'last', function(event, done) {
891 self.last(done);
892 });
893
894 // Disable
895 container.on(eventPrefix + 'disable', function() {
896 self.disable();
897 });
898
899 // Enable
900 container.on(eventPrefix + 'enable', function() {
901 self.enable();
902 });
903
904 // Refresh
905 container.on(eventPrefix + 'refresh', function(event, done) {
906 self.refresh(done);
907 });
908
909 // Show
910 container.on(eventPrefix + 'show', function() {
911 self.show();
912 });
913
914 // Hide
915 container.on(eventPrefix + 'hide', function() {
916 self.hide();
917 });
918
919 // Destroy
920 container.on(eventPrefix + 'destroy', function() {
921 self.destroy();
922 });
923
924 // Whether to load the default page
925 var validTotalPage = Math.max(self.getTotalPage(), 1)
926 var defaultPageNumber = attributes.pageNumber;
927
928 // Default pageNumber should be 1 when totalNumber is dynamic
929 if (self.isDynamicTotalNumber) {
930 if (attributes.resetPageNumberOnInit) defaultPageNumber = 1;
931 }
932
933 if (attributes.triggerPagingOnInit) {
934 container.trigger(eventPrefix + 'go', Math.min(defaultPageNumber, validTotalPage));
935 }
936 }
937 };
938
939 // Pagination has been initialized
940 if (container.data('pagination') && container.data('pagination').initialized === true) {
941 // Handle events
942 if (isNumeric(options)) {
943 // eg: container.pagination(5)
944 container.trigger.call(this, eventPrefix + 'go', options, arguments[1]);
945 return this;
946 } else if (typeof options === 'string') {
947 var args = Array.prototype.slice.apply(arguments);
948 args[0] = eventPrefix + args[0];
949
950 switch (options) {
951 case 'previous':
952 case 'next':
953 case 'first':
954 case 'last':
955 case 'go':
956 case 'disable':
957 case 'enable':
958 case 'refresh':
959 case 'show':
960 case 'hide':
961 case 'destroy':
962 container.trigger.apply(this, args);
963 break;
964 case 'getSelectedPageNum':
965 case 'getCurrentPageNum':
966 if (container.data('pagination').model) {
967 return container.data('pagination').model.pageNumber;
968 } else {
969 return container.data('pagination').attributes.pageNumber;
970 }
971 case 'getTotalPage':
972 return Math.ceil(container.data('pagination').model.totalNumber / container.data('pagination').model.pageSize);
973 case 'getSelectedPageData':
974 case 'getCurrentPageData':
975 return container.data('pagination').currentPageData;
976 // Whether pagination has been disabled
977 case 'isDisabled':
978 return container.data('pagination').model.disabled === true;
979 default:
980 throwError('Unknown action: ' + options);
981 }
982 return this;
983 } else {
984 // Uninstall the old instance before initializing a new one
985 uninstallPlugin(container);
986 }
987 } else {
988 if (!Helpers.isObject(options)) throwError('Illegal options');
989 }
990
991 // Check parameters
992 parameterChecker(attributes);
993
994 pagination.initialize();
995
996 return this;
997 };
998
999 // Instance defaults
1000 $.fn[pluginName].defaults = {
1001
1002 // Data source
1003 // Array | String | Function | Object
1004 //dataSource: '',
1005
1006 // String | Function
1007 //locator: 'data',
1008
1009 // Function
1010 //totalNumberLocator: function() {},
1011
1012 // Total number of data items
1013 totalNumber: 0,
1014
1015 // Default page number
1016 pageNumber: 1,
1017
1018 // Number of data items per page
1019 pageSize: 10,
1020
1021 // Page range (pages around current page)
1022 pageRange: 2,
1023
1024 // Whether to display the 'Previous' button
1025 showPrevious: true,
1026
1027 // Whether to display the 'Next' button
1028 showNext: true,
1029
1030 // Whether to display the page buttons
1031 showPageNumbers: true,
1032
1033 showNavigator: false,
1034
1035 // Whether to display the 'Go' input
1036 showGoInput: false,
1037
1038 // Whether to display the 'Go' button
1039 showGoButton: false,
1040
1041 showSizeChanger: false,
1042
1043 sizeChangerOptions: [10, 20, 50, 100],
1044
1045 // Page link
1046 pageLink: '',
1047
1048 // 'Previous' text
1049 prevText: '&lsaquo;',
1050
1051 // 'Next' text
1052 nextText: '&rsaquo;',
1053
1054 // 'First' text
1055 firstText: '&laquo;',
1056
1057 // 'Last' text
1058 lastText: '&raquo;',
1059
1060 // Ellipsis text
1061 ellipsisText: '...',
1062
1063 // 'Go' button text
1064 goButtonText: 'Go',
1065
1066 // Additional class name(s) for the Pagination container
1067 //className: '',
1068
1069 classPrefix: 'paginationjs',
1070
1071 activeClassName: 'active',
1072
1073 // class name when disabled
1074 disableClassName: 'disabled',
1075
1076 //ulClassName: '',
1077
1078 //pageClassName: '',
1079
1080 //prevClassName: '',
1081
1082 //nextClassName: '',
1083
1084 formatNavigator: 'Total <%= totalNumber %> items',
1085
1086 formatGoInput: '<%= input %>',
1087
1088 formatGoButton: '<%= button %>',
1089
1090 // position in the container
1091 position: 'bottom',
1092
1093 // Auto hide previous button when current page is the first
1094 autoHidePrevious: false,
1095
1096 // Auto hide next button when current page is the last
1097 autoHideNext: false,
1098
1099 //header: '',
1100
1101 //footer: '',
1102
1103 //alias: {},
1104
1105 // Whether to trigger pagination at initialization
1106 triggerPagingOnInit: true,
1107
1108 // Whether to reset page number at initialization, it works only if dataSource is a URL and totalNumberLocator is specified
1109 resetPageNumberOnInit: true,
1110
1111 // Whether to hide pagination when less than one page
1112 hideOnlyOnePage: false,
1113
1114 hideFirstOnEllipsisShow: false,
1115
1116 hideLastOnEllipsisShow: false,
1117
1118 // Customize item's innerHTML
1119 callback: function() {}
1120 };
1121
1122 // Hook register
1123 $.fn[pluginHookMethod] = function(hook, callback) {
1124 if (arguments.length < 2) {
1125 throwError('Expect 2 arguments at least.');
1126 }
1127
1128 if (typeof callback !== 'function') {
1129 throwError('callback should be a function.');
1130 }
1131
1132 var container = $(this);
1133 var paginationData = container.data('pagination');
1134
1135 if (!paginationData) {
1136 container.data('pagination', {});
1137 paginationData = container.data('pagination');
1138 }
1139
1140 !paginationData.hooks && (paginationData.hooks = {});
1141
1142 //paginationData.hooks[hook] = callback;
1143 paginationData.hooks[hook] = paginationData.hooks[hook] || [];
1144 paginationData.hooks[hook].push(callback);
1145
1146 };
1147
1148 // Static method
1149 $[pluginName] = function(selector, options) {
1150 if (arguments.length < 2) {
1151 throwError('Requires two parameters.');
1152 }
1153
1154 var container;
1155
1156 // 'selector' is a jQuery object
1157 if (typeof selector !== 'string' && selector instanceof jQuery) {
1158 container = selector;
1159 } else {
1160 container = $(selector);
1161 }
1162
1163 if (!container.length) return;
1164
1165 container.pagination(options);
1166
1167 return container;
1168 };
1169
1170 // ============================================================
1171 // helpers
1172 // ============================================================
1173
1174 var Helpers = {};
1175
1176 // Throw error
1177 function throwError(content) {
1178 throw new Error('Pagination: ' + content);
1179 }
1180
1181 // Check parameters
1182 function parameterChecker(args) {
1183 if (!args.dataSource) {
1184 throwError('"dataSource" is required.');
1185 }
1186
1187 if (typeof args.dataSource === 'string') {
1188 if (args.totalNumberLocator === undefined) {
1189 if (args.totalNumber === undefined) {
1190 throwError('"totalNumber" is required.');
1191 } else if (!isNumeric(args.totalNumber)) {
1192 throwError('"totalNumber" is incorrect. Expect numberic type');
1193 }
1194 } else {
1195 if (typeof args.totalNumberLocator !== 'function') {
1196 throwError('"totalNumberLocator" should be a Function.');
1197 }
1198 }
1199 } else if (Helpers.isObject(args.dataSource)) {
1200 if (typeof args.locator === 'undefined') {
1201 throwError('"dataSource" is an Object, please specify a "locator".');
1202 } else if (typeof args.locator !== 'string' && typeof args.locator !== 'function') {
1203 throwError('' + args.locator + ' is incorrect. Expect string or function type');
1204 }
1205 }
1206
1207 if (args.formatResult !== undefined && typeof args.formatResult !== 'function') {
1208 throwError('"formatResult" should be a Function.');
1209 }
1210
1211 if (args.onError !== undefined && typeof args.onError !== 'function') {
1212 throwError('"onError" should be a Function.');
1213 }
1214 }
1215
1216 // uninstall plugin
1217 function uninstallPlugin(target) {
1218 var events = ['go', 'previous', 'next', 'first', 'last', 'disable', 'enable', 'refresh', 'show', 'hide', 'destroy'];
1219
1220 // off all events
1221 $.each(events, function(index, value) {
1222 target.off(eventPrefix + value);
1223 });
1224
1225 // reset pagination data
1226 target.data('pagination', {});
1227
1228 // remove pagination element
1229 $('.paginationjs', target).remove();
1230 }
1231
1232 // Object type detection
1233 function getObjectType(object, tmp) {
1234 return ( (tmp = typeof(object)) == "object" ? object == null && "null" || Object.prototype.toString.call(object).slice(8, -1) : tmp ).toLowerCase();
1235 }
1236
1237 function isNumeric(n) {
1238 return !isNaN(parseFloat(n)) && isFinite(n);
1239 }
1240
1241 $.each(['Object', 'Array', 'String'], function(index, name) {
1242 Helpers['is' + name] = function(object) {
1243 return getObjectType(object) === name.toLowerCase();
1244 };
1245 });
1246
1247 /*
1248 * export via AMD or CommonJS
1249 * */
1250 if (typeof define === 'function' && define.amd) {
1251 define(function() {
1252 return $;
1253 });
1254 }
1255
1256})(this, window.jQuery);