dirPagination.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /**
  2. * dirPagination - AngularJS module for paginating (almost) anything.
  3. *
  4. *
  5. * Credits
  6. * =======
  7. *
  8. * Daniel Tabuenca: https://groups.google.com/d/msg/angular/an9QpzqIYiM/r8v-3W1X5vcJ
  9. * for the idea on how to dynamically invoke the ng-repeat directive.
  10. *
  11. * I borrowed a couple of lines and a few attribute names from the AngularUI Bootstrap project:
  12. * https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
  13. *
  14. * Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
  15. */
  16. (function() {
  17. /**
  18. * Config
  19. */
  20. var moduleName = 'angularUtils.directives.dirPagination';
  21. var DEFAULT_ID = '__default';
  22. /**
  23. * Module
  24. */
  25. var module;
  26. try {
  27. module = angular.module(moduleName);
  28. } catch(err) {
  29. // named module does not exist, so create one
  30. module = angular.module(moduleName, []);
  31. }
  32. module
  33. .directive('dirPaginate', ['$compile', '$parse', 'paginationService', dirPaginateDirective])
  34. .directive('dirPaginateNoCompile', noCompileDirective)
  35. .directive('dirPaginationControls', ['paginationService', 'paginationTemplate', dirPaginationControlsDirective])
  36. .filter('itemsPerPage', ['paginationService', itemsPerPageFilter])
  37. .service('paginationService', paginationService)
  38. .provider('paginationTemplate', paginationTemplateProvider)
  39. .run(['$templateCache',dirPaginationControlsTemplateInstaller]);
  40. function dirPaginateDirective($compile, $parse, paginationService) {
  41. return {
  42. terminal: true,
  43. multiElement: true,
  44. compile: dirPaginationCompileFn
  45. };
  46. function dirPaginationCompileFn(tElement, tAttrs){
  47. var expression = tAttrs.dirPaginate;
  48. // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211
  49. var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
  50. var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/;
  51. if (match[2].match(filterPattern) === null) {
  52. throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
  53. }
  54. var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
  55. var collectionGetter = $parse(itemsPerPageFilterRemoved);
  56. addNoCompileAttributes(tElement);
  57. // If any value is specified for paginationId, we register the un-evaluated expression at this stage for the benefit of any
  58. // dir-pagination-controls directives that may be looking for this ID.
  59. var rawId = tAttrs.paginationId || DEFAULT_ID;
  60. paginationService.registerInstance(rawId);
  61. return function dirPaginationLinkFn(scope, element, attrs){
  62. // Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and
  63. // potentially register a new ID if it evaluates to a different value than the rawId.
  64. var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID;
  65. paginationService.registerInstance(paginationId);
  66. var repeatExpression = getRepeatExpression(expression, paginationId);
  67. addNgRepeatToElement(element, attrs, repeatExpression);
  68. removeTemporaryAttributes(element);
  69. var compiled = $compile(element);
  70. var currentPageGetter = makeCurrentPageGetterFn(scope, attrs, paginationId);
  71. paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope);
  72. if (typeof attrs.totalItems !== 'undefined') {
  73. paginationService.setAsyncModeTrue(paginationId);
  74. scope.$watch(function() {
  75. return $parse(attrs.totalItems)(scope);
  76. }, function (result) {
  77. if (0 <= result) {
  78. paginationService.setCollectionLength(paginationId, result);
  79. }
  80. });
  81. } else {
  82. scope.$watchCollection(function() {
  83. return collectionGetter(scope);
  84. }, function(collection) {
  85. if (collection) {
  86. paginationService.setCollectionLength(paginationId, collection.length);
  87. }
  88. });
  89. }
  90. // Delegate to the link function returned by the new compilation of the ng-repeat
  91. compiled(scope);
  92. };
  93. }
  94. /**
  95. * If a pagination id has been specified, we need to check that it is present as the second argument passed to
  96. * the itemsPerPage filter. If it is not there, we add it and return the modified expression.
  97. *
  98. * @param expression
  99. * @param paginationId
  100. * @returns {*}
  101. */
  102. function getRepeatExpression(expression, paginationId) {
  103. var repeatExpression,
  104. idDefinedInFilter = !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/);
  105. if (paginationId !== DEFAULT_ID && !idDefinedInFilter) {
  106. repeatExpression = expression.replace(/(\|\s*itemsPerPage\s*:[^|]*)/, "$1 : '" + paginationId + "'");
  107. } else {
  108. repeatExpression = expression;
  109. }
  110. return repeatExpression;
  111. }
  112. /**
  113. * Adds the ng-repeat directive to the element. In the case of multi-element (-start, -end) it adds the
  114. * appropriate multi-element ng-repeat to the first and last element in the range.
  115. * @param element
  116. * @param attrs
  117. * @param repeatExpression
  118. */
  119. function addNgRepeatToElement(element, attrs, repeatExpression) {
  120. if (element[0].hasAttribute('dir-paginate-start') || element[0].hasAttribute('data-dir-paginate-start')) {
  121. // using multiElement mode (dir-paginate-start, dir-paginate-end)
  122. attrs.$set('ngRepeatStart', repeatExpression);
  123. element.eq(element.length - 1).attr('ng-repeat-end', true);
  124. } else {
  125. attrs.$set('ngRepeat', repeatExpression);
  126. }
  127. }
  128. /**
  129. * Adds the dir-paginate-no-compile directive to each element in the tElement range.
  130. * @param tElement
  131. */
  132. function addNoCompileAttributes(tElement) {
  133. angular.forEach(tElement, function(el) {
  134. if (el.nodeType === Node.ELEMENT_NODE) {
  135. angular.element(el).attr('dir-paginate-no-compile', true);
  136. }
  137. });
  138. }
  139. /**
  140. * Removes the variations on dir-paginate (data-, -start, -end) and the dir-paginate-no-compile directives.
  141. * @param element
  142. */
  143. function removeTemporaryAttributes(element) {
  144. angular.forEach(element, function(el) {
  145. if (el.nodeType === Node.ELEMENT_NODE) {
  146. angular.element(el).removeAttr('dir-paginate-no-compile');
  147. }
  148. });
  149. element.eq(0).removeAttr('dir-paginate-start').removeAttr('dir-paginate').removeAttr('data-dir-paginate-start').removeAttr('data-dir-paginate');
  150. element.eq(element.length - 1).removeAttr('dir-paginate-end').removeAttr('data-dir-paginate-end');
  151. }
  152. /**
  153. * Creates a getter function for the current-page attribute, using the expression provided or a default value if
  154. * no current-page expression was specified.
  155. *
  156. * @param scope
  157. * @param attrs
  158. * @param paginationId
  159. * @returns {*}
  160. */
  161. function makeCurrentPageGetterFn(scope, attrs, paginationId) {
  162. var currentPageGetter;
  163. if (attrs.currentPage) {
  164. currentPageGetter = $parse(attrs.currentPage);
  165. } else {
  166. // if the current-page attribute was not set, we'll make our own
  167. var defaultCurrentPage = paginationId + '__currentPage';
  168. scope[defaultCurrentPage] = 1;
  169. currentPageGetter = $parse(defaultCurrentPage);
  170. }
  171. return currentPageGetter;
  172. }
  173. }
  174. /**
  175. * This is a helper directive that allows correct compilation when in multi-element mode (ie dir-paginate-start, dir-paginate-end).
  176. * It is dynamically added to all elements in the dir-paginate compile function, and it prevents further compilation of
  177. * any inner directives. It is then removed in the link function, and all inner directives are then manually compiled.
  178. */
  179. function noCompileDirective() {
  180. return {
  181. priority: 5000,
  182. terminal: true
  183. };
  184. }
  185. function dirPaginationControlsTemplateInstaller($templateCache) {
  186. //往里面 拼入html页 分页
  187. $templateCache.put('angularUtils.directives.dirPagination.template', '<ul class="pagination" ng-if="1 < pages.length"><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == 1 }"><a ng-click="setCurrent(1)">首页</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a ng-click="setCurrent(pagination.current - 1)"><上一页</a></li><li ng-repeat="pageNumber in pages track by $index" ng-class="{ active : pagination.current == pageNumber, disabled : pageNumber == \'...\' }"><a ng-click="setCurrent(pageNumber)">{{ pageNumber }}</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a ng-click="setCurrent(pagination.current + 1)">下一页></a></li><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a ng-click="setCurrent(pagination.last)">尾页</a></li></ul>');
  188. //$templateCache.put('angularUtils.directives.dirPagination.template', '<ul class="pagination" ng-if="1 < pages.length"><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(1)">首页</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(pagination.current - 1)"><上一页</a></li><li ng-repeat="pageNumber in pages track by $index" ng-class="{ active : pagination.current == pageNumber, disabled : pageNumber == \'...\' }"><a href="" ng-click="setCurrent(pageNumber)">{{ pageNumber }}</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.current + 1)">下一页></a></li><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.last)">尾页</a></li></ul>');
  189. //$templateCache.put('angularUtils.directives.dirPagination.template', '<ul class="pagination" ng-if="1 < pages.length"><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(1)">首页</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(pagination.current - 1)"><上一页</a></li><li ng-repeat="pageNumber in pages track by $index" ng-class="{ active : pagination.current == pageNumber, disabled : pageNumber == \'...\' }"><a href="" ng-click="setCurrent(pageNumber)">{{ pageNumber }}</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.current + 1)">下一页></a></li><li ng-if="boundaryLinks" ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.last)">尾页</a></li><li><select><option value="10">10</option><option value="12">12</option><option value="15">15</option><option value="18">18</option><option value="20">20</option></select></li><li><label>共{{ctl.total_count}}条数据</label></li></ul>');
  190. }
  191. function dirPaginationControlsDirective(paginationService, paginationTemplate) {
  192. var numberRegex = /^\d+$/;
  193. return {
  194. restrict: 'AE',
  195. templateUrl: function(elem, attrs) {
  196. return attrs.templateUrl || paginationTemplate.getPath();
  197. },
  198. scope: {
  199. maxSize: '=?',
  200. onPageChange: '&?',
  201. paginationId: '=?'
  202. },
  203. link: dirPaginationControlsLinkFn
  204. };
  205. function dirPaginationControlsLinkFn(scope, element, attrs) {
  206. // rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has
  207. // not yet been linked (e.g. if it is inside an ng-if block), and in that case it prevents this controls directive from assuming that there is
  208. // no corresponding dir-paginate directive and wrongly throwing an exception.
  209. var rawId = attrs.paginationId || DEFAULT_ID;
  210. var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID;
  211. if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) {
  212. var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' ';
  213. throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.';
  214. }
  215. if (!scope.maxSize) { scope.maxSize = 9; }
  216. scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
  217. scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;
  218. var paginationRange = Math.max(scope.maxSize, 5);
  219. scope.pages = [];
  220. scope.pagination = {
  221. last: 1,
  222. current: 1
  223. };
  224. scope.range = {
  225. lower: 1,
  226. upper: 1,
  227. total: 1
  228. };
  229. scope.$watch(function() {
  230. return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
  231. }, function(length) {
  232. if (0 < length) {
  233. generatePagination();
  234. }
  235. });
  236. scope.$watch(function() {
  237. return (paginationService.getItemsPerPage(paginationId));
  238. }, function(current, previous) {
  239. if (current != previous && typeof previous !== 'undefined') {
  240. goToPage(scope.pagination.current);
  241. }
  242. });
  243. scope.$watch(function() {
  244. return paginationService.getCurrentPage(paginationId);
  245. }, function(currentPage, previousPage) {
  246. if (currentPage != previousPage) {
  247. goToPage(currentPage);
  248. }
  249. });
  250. scope.setCurrent = function (num) {
  251. if (isValidPageNumber(num)) {
  252. num = parseInt(num, 10);
  253. paginationService.setCurrentPage(paginationId, num);
  254. }
  255. };
  256. function goToPage(num) {
  257. if (isValidPageNumber(num)) {
  258. scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
  259. scope.pagination.current = num;
  260. updateRangeValues();
  261. // if a callback has been set, then call it with the page number as an argument
  262. if (scope.onPageChange) {
  263. scope.onPageChange({ newPageNumber : num });
  264. }
  265. }
  266. }
  267. function generatePagination() {
  268. var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;
  269. scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
  270. scope.pagination.current = page;
  271. scope.pagination.last = scope.pages[scope.pages.length - 1];
  272. if (scope.pagination.last < scope.pagination.current) {
  273. scope.setCurrent(scope.pagination.last);
  274. } else {
  275. updateRangeValues();
  276. }
  277. }
  278. /**
  279. * This function updates the values (lower, upper, total) of the `scope.range` object, which can be used in the pagination
  280. * template to display the current page range, e.g. "showing 21 - 40 of 144 results";
  281. */
  282. function updateRangeValues() {
  283. var currentPage = paginationService.getCurrentPage(paginationId),
  284. itemsPerPage = paginationService.getItemsPerPage(paginationId),
  285. totalItems = paginationService.getCollectionLength(paginationId);
  286. scope.range.lower = (currentPage - 1) * itemsPerPage + 1;
  287. scope.range.upper = Math.min(currentPage * itemsPerPage, totalItems);
  288. scope.range.total = totalItems;
  289. }
  290. function isValidPageNumber(num) {
  291. return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
  292. }
  293. }
  294. /**
  295. * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
  296. * links used in pagination
  297. *
  298. * @param currentPage
  299. * @param rowsPerPage
  300. * @param paginationRange
  301. * @param collectionLength
  302. * @returns {Array}
  303. */
  304. function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
  305. var pages = [];
  306. var totalPages = Math.ceil(collectionLength / rowsPerPage);
  307. var halfWay = Math.ceil(paginationRange / 2);
  308. var position;
  309. if (currentPage <= halfWay) {
  310. position = 'start';
  311. } else if (totalPages - halfWay < currentPage) {
  312. position = 'end';
  313. } else {
  314. position = 'middle';
  315. }
  316. var ellipsesNeeded = paginationRange < totalPages;
  317. var i = 1;
  318. while (i <= totalPages && i <= paginationRange) {
  319. var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);
  320. var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
  321. var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
  322. if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
  323. pages.push('...');
  324. } else {
  325. pages.push(pageNumber);
  326. }
  327. i ++;
  328. }
  329. return pages;
  330. }
  331. /**
  332. * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
  333. *
  334. * @param i
  335. * @param currentPage
  336. * @param paginationRange
  337. * @param totalPages
  338. * @returns {*}
  339. */
  340. function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
  341. var halfWay = Math.ceil(paginationRange/2);
  342. if (i === paginationRange) {
  343. return totalPages;
  344. } else if (i === 1) {
  345. return i;
  346. } else if (paginationRange < totalPages) {
  347. if (totalPages - halfWay < currentPage) {
  348. return totalPages - paginationRange + i;
  349. } else if (halfWay < currentPage) {
  350. return currentPage - halfWay + i;
  351. } else {
  352. return i;
  353. }
  354. } else {
  355. return i;
  356. }
  357. }
  358. }
  359. /**
  360. * This filter slices the collection into pages based on the current page number and number of items per page.
  361. * @param paginationService
  362. * @returns {Function}
  363. */
  364. function itemsPerPageFilter(paginationService) {
  365. return function(collection, itemsPerPage, paginationId) {
  366. if (typeof (paginationId) === 'undefined') {
  367. paginationId = DEFAULT_ID;
  368. }
  369. if (!paginationService.isRegistered(paginationId)) {
  370. throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
  371. }
  372. var end;
  373. var start;
  374. if (collection instanceof Array) {
  375. itemsPerPage = parseInt(itemsPerPage) || 9999999999;
  376. if (paginationService.isAsyncMode(paginationId)) {
  377. start = 0;
  378. } else {
  379. start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
  380. }
  381. end = start + itemsPerPage;
  382. paginationService.setItemsPerPage(paginationId, itemsPerPage);
  383. return collection.slice(start, end);
  384. } else {
  385. return collection;
  386. }
  387. };
  388. }
  389. /**
  390. * This service allows the various parts of the module to communicate and stay in sync.
  391. */
  392. function paginationService() {
  393. var instances = {};
  394. var lastRegisteredInstance;
  395. this.registerInstance = function(instanceId) {
  396. if (typeof instances[instanceId] === 'undefined') {
  397. instances[instanceId] = {
  398. asyncMode: false
  399. };
  400. lastRegisteredInstance = instanceId;
  401. }
  402. };
  403. this.isRegistered = function(instanceId) {
  404. return (typeof instances[instanceId] !== 'undefined');
  405. };
  406. this.getLastInstanceId = function() {
  407. return lastRegisteredInstance;
  408. };
  409. this.setCurrentPageParser = function(instanceId, val, scope) {
  410. instances[instanceId].currentPageParser = val;
  411. instances[instanceId].context = scope;
  412. };
  413. this.setCurrentPage = function(instanceId, val) {
  414. instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
  415. };
  416. this.getCurrentPage = function(instanceId) {
  417. var parser = instances[instanceId].currentPageParser;
  418. return parser ? parser(instances[instanceId].context) : 1;
  419. };
  420. this.setItemsPerPage = function(instanceId, val) {
  421. instances[instanceId].itemsPerPage = val;
  422. };
  423. this.getItemsPerPage = function(instanceId) {
  424. return instances[instanceId].itemsPerPage;
  425. };
  426. this.setCollectionLength = function(instanceId, val) {
  427. instances[instanceId].collectionLength = val;
  428. };
  429. this.getCollectionLength = function(instanceId) {
  430. return instances[instanceId].collectionLength;
  431. };
  432. this.setAsyncModeTrue = function(instanceId) {
  433. instances[instanceId].asyncMode = true;
  434. };
  435. this.isAsyncMode = function(instanceId) {
  436. return instances[instanceId].asyncMode;
  437. };
  438. }
  439. /**
  440. * This provider allows global configuration of the template path used by the dir-pagination-controls directive.
  441. */
  442. function paginationTemplateProvider() {
  443. var templatePath = 'angularUtils.directives.dirPagination.template';
  444. this.setPath = function(path) {
  445. templatePath = path;
  446. };
  447. this.$get = function() {
  448. return {
  449. getPath: function() {
  450. return templatePath;
  451. }
  452. };
  453. };
  454. }
  455. })();