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 <[email protected]> 15 * License: This code is made available under the MIT license, so feel free to use any of these is your projects. 16 */ 17 18(function () { 19 20 /** 21 * Config 22 */ 23 var moduleName = 'angularUtils.directives.dirPagination'; 24 var DEFAULT_ID = '__default'; 25 26 /** 27 * Module 28 */ 29 var module; 30 try { 31 module = angular.module(moduleName); 32 } catch (err) { 33 // named module does not exist, so create one 34 module = angular.module(moduleName, []); 35 } 36 37 module.directive('dirPaginate', ['$compile', '$parse', 'paginationService', function ($compile, $parse, paginationService) { 38 39 return { 40 terminal: true, 41 multiElement: true, 42 priority: 5000, // This setting is used in conjunction with the later call to $compile() to prevent infinite recursion of compilation 43 compile: function dirPaginationCompileFn(tElement, tAttrs
43) { 44 45 var expression = tAttrs.dirPaginate; 46 // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211 47 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 48 49 var filterPattern = /\|\s*itemsPerPage\s*:[^|]*/; 50 if (match[2].match(filterPattern) === null) { 51 throw 'pagination directive: the \'itemsPerPage\' filter must be set.'; 52 } 53 var itemsPerPageFilterRemoved = match[2].replace(filterPattern, ''); 54 var collectionGetter = $parse(itemsPerPageFilterRemoved); 55 56 // If any value is specified for paginationId, we register the un-evaluated expression at this stage for the benefit of any 57 // dir-pagination-controls directives that may be looking for this ID. 58 var rawId = tAttrs.paginationId || DEFAULT_ID; 59 paginationService.registerInstance(rawId); 60 61 return function dirPaginationLinkFn(scope, element, attrs) { 62 63 // Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and 64 // potentially register a new ID if it evaluates to a different value than the rawId. 65 var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID; 66 paginationService.registerInstance(paginationId); 67 68 var repeatExpression; 69 var idDefinedInFilter = !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/); 70 if (paginationId !== DEFAULT_ID && !idDefinedInFilter) { 71 repeatExpression = expression.replace(/(\|\s*itemsPerPage\s*:[^|]*)/, "$1 : '" + paginationId + "'"); 72 } else { 73 repeatExpression = expression; 74 } 75 76 // Add ng-repeat to the dom element 77 if (element[0].hasAttribute('dir-paginate-start') || element[0].hasAttribute('data-dir-paginate-start')) { 78 // using multiElement mode (dir-paginate-start, dir-paginate-end) 79 attrs.$set('ngRepeatStart', repeatExpression); 80 element.eq(element.length - 1).attr('ng-repeat-end', true); 81 } else { 82 attrs.$set('ngRepeat', repeatExpression); 83 } 84 85 var compiled = $compile(element, false, 5000); // we manually compile the element again, as we have now added ng-repeat. Priority less than 5000 prevents infinite recursion of compiling dirPaginate 86 87 var currentPageGetter; 88 if (attrs.currentPage) { 89 currentPageGetter = $parse(attrs.currentPage); 90 } else { 91 // if the current-page attribute was not set, we'll make our own 92 var defaultCurrentPage = paginationId + '__currentPage'; 93 scope[defaultCurrentPage] = 1; 94 currentPageGetter = $parse(defaultCurrentPage); 95 } 96 paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope); 97 98 if (typeof attrs.totalItems !== 'undefined') { 99 paginationService.setAsyncModeTrue(paginationId); 100 scope.$watch(function () { 101 return $parse(attrs.totalItems)(scope); 102 }, function (result) { 103 if (0 <= result) { 104 paginationService.setCollectionLength(paginationId, result); 105 } 106 }); 107 } else { 108 scope.$watchCollection(function () { 109 return collectionGetter(scope); 110 }, function (collection) { 111 if (collection) { 112 paginationService.setCollectionLength(paginationId, collection.length); 113 } 114 }); 115 } 116 117 // Delegate to the link function returned by the new compilation of the ng-repeat 118 compiled(scope); 119 }; 120 } 121 }; 122 }]); 123 124 module.directive('dirPaginationControls', ['paginationService', '$location', '$rootScope', function (paginationService, $location, $rootScope) { 125 126 var numberRegex = /^\d+$/; 127 128 /** 129 * Generate an array of page numbers (or the '...' string)
129which is used in an ng-repeat to generate the 130 * links used in pagination 131 * 132 * @param currentPage 133 * @param rowsPerPage 134 * @param paginationRange 135 * @param collectionLength 136 * @returns {Array} 137 */ 138 function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) { 139 var pages = []; 140 var totalPages = Math.ceil(collectionLength / rowsPerPage); 141 var halfWay = Math.ceil(paginationRange / 2); 142 var position; 143 144 if (currentPage <= halfWay) { 145 position = 'start'; 146 } else if (totalPages - halfWay < currentPage) { 147 position = 'end'; 148 } else { 149 position = 'middle'; 150 } 151 152 var ellipsesNeeded = paginationRange < totalPages; 153 var i = 1; 154 while (i <= totalPages && i <= paginationRange) { 155 var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages); 156 157 var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end')); 158 var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start')); 159 if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) { 160 pages.push('...'); 161 } else { 162 pages.push(pageNumber); 163 } 164 i++; 165 } 166 return pages; 167 } 168 169 /** 170 * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position. 171 * 172 * @param i 173 * @param currentPage 174 * @param paginationRange 175 * @param totalPages 176 * @returns {*} 177 */ 178 function calculatePageNumber(i, currentPage, paginationRange, totalPages) { 179 var halfWay = Math.ceil(paginationRange / 2); 180 if (i === paginationRange) { 181 return totalPages; 182 } else if (i === 1) { 183 return i; 184 } else if (paginationRange < totalPages) { 185 if (totalPages - halfWay < currentPage) { 186 return totalPages - paginationRange + i; 187 } else if (halfWay < currentPage) { 188 return currentPage - halfWay + i; 189 } else { 190 return i; 191 } 192 } else { 193 return i; 194 } 195 } 196 197 return { 198 restrict: 'AE', 199 template: "\n<ul class=\"pagination\" >\n <li ng-if=\"boundaryLinks\" ng-class=\"{ disabled : pagination.current == 1 }\">\n <a href=\"\" ng-click=\"setCurrent(1)\">«</a>\n </li>\n <li ng-if=\"directionLinks\" ng-class=\"{ disabled : pagination.current == 1 }\">\n <a href=\"\" ng-click=\"setCurrent(pagination.current - 1)\">‹</a>\n </li>\n <li ng-repeat=\"pageNumber in pages track by $index\" ng-class=\"{ active : pagination.current == pageNumber, disabled : pageNumber == '...' }\">\n <a href=\"\" ng-click=\"setCurrent(pageNumber)\">{{ pageNumber }}</a>\n </li>\n\n <li ng-if=\"directionLinks\" ng-class=\"{ disabled : pagination.current == pagination.last }\">\n <a href=\"\" ng-click=\"setCurrent(pagination.current + 1)\">›</a>\n </li>\n <li ng-if=\"boundaryLinks\" ng-class=\"{ disabled : pagination.current == pagination.last }\">\n <a href=\"\" ng-click=\"setCurrent(pagination.last)\">»</a>\n </li>\n</ul>\n", 200 scope: { 201 maxSize: '=?', 202 onPageChange: '&?', 203 paginationId: '=?' 204 }, 205 link: function dirPaginationControlsLinkFn(scope, element, attrs) { 206 207 // rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has 208 // 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 209 // no corresponding dir-paginate directive and wrongly throwing an exception. 210 var rawId = attrs.paginationId || DEFAULT_ID; 211 var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID; 212 213 if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) { 214 var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' '; 215 throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.'; 216 } 217 218 if (!scope.maxSize) { scope.maxSize = 9; } 219 scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true; 220 scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false; 221 222 var paginationRange = Math.max(scope.maxSize, 5); 223 scope.pages = []; 224 scope.pagination = { 225 last: 1, 226 current: 1 227 }; 228 scope.range = { 229 lower: 1, 230 upper: 1, 231 total: 1 232 }; 233 234 scope.$watch(function () { 235 return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId); 236 }, function (length) { 237 if (0 < length) { 238 generatePagination(); 239 } 240 }); 241 242 scope.$watch(function () { 243 return (paginationService.getItemsPerPage(paginationId)); 244 }, function (current, previous) { 245 if (current != previous) { 246 goToPage(scope.pagination.current); 247 } 248 }); 249 250 scope.$watch(function () { 251 return paginationService.getCurrentPage(paginationId); 252 }, function (currentPage, previousPage) { 253 if (currentPage != previousPage) { 254 goToPage(currentPage); 255 } 256 }); 257 258 scope.setCurrent = function (num) { 259 if (isValidPageNumber(num)) { 260 paginationService.setCurrentPage(paginationId, num); 261 } 262 }; 263 264 function goToPage(num) { 265 if (isValidPageNumber(num)) { 266 scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); 267 scope.pagination.current = num; 268 updateRangeValues();
269 270 // if a callback has been set, then call it with the page number as an argument 271 if (scope.onPageChange) { 272 scope.onPageChange({ newPageNumber: num }); 273 } 274 $('html,body').scrollTop(0); 275 } 276 } 277 278 function generatePagination() { 279 var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1; 280 281 scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); 282 scope.pagination.current = page; 283 scope.pagination.last = scope.pages[scope.pages.length - 1]; 284 if (scope.pagination.last < scope.pagination.current) { 285 scope.setCurrent(scope.pagination.last); 286 } else { 287 updateRangeValues(); 288 } 289 } 290 291 /** 292 * This function updates the values (lower, upper, total) of the `scope.range` object, which can be used in the pagination 293 * template to display the current page range, e.g. "showing 21 - 40 of 144 results"; 294 */ 295 function updateRangeValues() { 296 var currentPage = paginationService.getCurrentPage(paginationId), 297 itemsPerPage = paginationService.getItemsPerPage(paginationId), 298 totalItems = paginationService.getCollectionLength(paginationId); 299 300 scope.range.lower = (currentPage - 1) * itemsPerPage + 1; 301 scope.range.upper = Math.min(currentPage * itemsPerPage, totalItems); 302 scope.range.total = totalItems; 303 } 304 305 function isValidPageNumber(num) { 306 return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last)); 307 } 308 } 309 }; 310 }]); 311 312 module.filter('itemsPerPage', ['paginationService', function (paginationService) { 313 314 return function (collection, itemsPerPage, paginationId) { 315 if (typeof (paginationId) === 'undefined') { 316 paginationId = DEFAULT_ID; 317 } 318 if (!paginationService.isRegistered(paginationId)) { 319 throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.'; 320 } 321 var end; 322 var start; 323 if (collection instanceof Array) { 324 itemsPerPage = parseInt(itemsPerPage) || 9999999999; 325 if (paginationService.isAsyncMode(paginationId)) { 326 start = 0; 327 } else { 328 start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage; 329 } 330 end = start + itemsPerPage; 331 paginationService.setItemsPerPage(paginationId, itemsPerPage); 332 333 return collection.slice(start, end); 334 } else { 335 return collection; 336 } 337 }; 338 }]); 339 340 module.service('paginationService', function () { 341 342 var instances = {}; 343 var lastRegisteredInstance; 344 345 this.registerInstance = function (instanceId) { 346 if (typeof instances[instanceId] === 'undefined') { 347 instances[instanceId] = { 348 asyncMode: false 349 }; 350 lastRegisteredInstance = instanceId; 351 } 352 }; 353 354 this.isRegistered = function (instanceId) { 355 return (typeof instances[instanceId] !== 'undefined'); 356 }; 357 358 this.getLastInstanceId = function () { 359 return lastRegisteredInstance; 360 }; 361 362 this.setCurrentPageParser = function (instanceId, val, scope) { 363 instances[instanceId].currentPageParser = val; 364 instances[instanceId].context = scope; 365 }; 366 this.setCurrentPage = function (instanceId, val) { 367 instances[instanceId].currentPageParser.assign(instances[instanceId].context, val); 368 }; 369 this.getCurrentPage = function (instanceId) { 370 var parser = instances[instanceId].currentPageParser; 371 return parser ? parser(instances[instanceId].context) : 1; 372 }; 373 374 this.setItemsPerPage = function (instanceId, val) { 375 instances[instanceId].itemsPerPage = val; 376 }; 377 this.getItemsPerPage = function (instanceId) { 378 return instances[instanceId].itemsPerPage; 379 }; 380 381 this.setCollectionLength = function (instanceId, val) { 382 instances[instanceId].collectionLength = val; 383 }; 384 this.getCollectionLength = function (instanceId) { 385 return instances[instanceId].collectionLength; 386 }; 387 388 this.setAsyncModeTrue = function (instanceId) { 389 instances[instanceId].asyncMode = true; 390 }; 391 392 this.isAsyncMode = function (instanceId) { 393 return instances[instanceId].asyncMode; 394 }; 395 }); 396})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.