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