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 templatePath = 'directives/pagination/dirPagination.tpl.html'; 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.directive('dirPaginate', ['$compile', '$parse', '$timeout', 'paginationService', function($compile, $parse, $timeout, paginationService) { 37 return { 38 terminal: true, 39 compile: function(element, attrs){ 40 attrs.$set('ngRepeat', attrs.dirPaginate); // Add ng-repeat to the dom element 41 element.removeAttr(attrs.$attr.dirPaginate); // Remove the dir-paginate attribute to prevent infinite recursion of compilation 42 43 var expression = attrs.dirPaginate; 44 // regex taken directly from https://github.com/angular/angular.js/blob/master/src/ng/directive/ngRepeat.js#L211 45 var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?\s*$/); 46 47 var filterPattern = /\|\s*itemsPerPage:[^|]*/; 48 if (match[2].match(filterPattern) === null) { 49 throw 'pagination directive: the \'itemsPerPage\' filter must be set.'; 50 } 51 var itemsPerPageFilterRemoved = match[2].replace(filterPattern, ''); 52 var collectionGetter = $parse(itemsPerPageFilterRemoved); 53 54 return function(scope, element, attrs){ 55 var paginationId; 56 var compiled = $compile(element); // we manually compile the element again, as we have now swapped dir-paginate for an ng-repeat 57 58 paginationId = attrs.paginationId || '__default'; 59 paginationService.registerInstance(paginationId); 60 61 var currentPageGetter; 62 if (attrs.currentPage) { 63 currentPageGetter = $parse(attrs.currentPage); 64 } else { 65 // if the current-page attribute was not set, we'll make our own 66 var defaultCurrentPage = paginationId + '__currentPage'; 67 scope[defaultCurrentPage] = 1; 68 currentPageGetter = $parse(defaultCurrentPage); 69 } 70 paginationService.setCurrentPageParser(paginationId, currentPageGetter, scope); 71 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 91 // Delegate to the link function returned by the new compilation of the ng-repeat 92 compiled(scope); 93 }; 94 } 95 }; 96 }]); 97 98 module.directive('dirPaginationControls', ['paginationService', function(paginationService) { 99 var numberRegex = /^\d+$/; 100 /** 101 * Generate an array of page numbers (or the '...' string)
101which is used in an ng-repeat to generate the 102 * links used in pagination 103 * 104 * @param currentPage 105 * @param rowsPerPage 106 * @param paginationRange 107 * @param collectionLength 108 * @returns {Array} 109 */ 110 function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) { 111 var pages = []; 112 var totalPages = Math.ceil(collectionLength / rowsPerPage); 113 var halfWay = Math.ceil(paginationRange / 2); 114 var position; 115 116 if (currentPage <= halfWay) { 117 position = 'start'; 118 } else if (totalPages - halfWay < currentPage) { 119 position = 'end'; 120 } else { 121 position = 'middle'; 122 } 123 124 var ellipsesNeeded = paginationRange < totalPages; 125 var i = 1; 126 while (i <= totalPages && i <= paginationRange) { 127 var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages); 128 129 var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end')); 130 var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start')); 131 if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) { 132 pages.push('...'); 133 } else { 134 pages.push(pageNumber); 135 } 136 i ++; 137 } 138 return pages; 139 } 140 141 /** 142 * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position. 143 * 144 * @param i 145 * @param currentPage 146 * @param paginationRange 147 * @param totalPages 148 * @returns {*} 149 */ 150 function calculatePageNumber(i, currentPage, paginationRange, totalPages) { 151 var halfWay = Math.ceil(paginationRange/2); 152 if (i === paginationRange) { 153 return totalPages; 154 } else if (i === 1) { 155 return i; 156 } else if (paginationRange < totalPages) { 157 if (totalPages - halfWay < currentPage) { 158 return totalPages - paginationRange + i; 159 } else if (halfWay < currentPage) { 160 return currentPage - halfWay + i; 161 } else { 162 return i; 163 } 164 } else { 165 return i; 166 } 167 } 168 169 return { 170 restrict: 'AE', 171 templateUrl: function(elem, attrs) { 172 return attrs.templateUrl || templatePath; 173 }, 174 scope: { 175 maxSize: '=?', 176 onPageChange: '&?' 177 }, 178 link: function(scope, element, attrs) { 179 var paginationId; 180 paginationId = attrs.paginationId || '__default'; 181 if (!scope.maxSize) { scope.maxSize = 9; } 182 scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true; 183 scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false; 184 185 if (!paginationService.isRegistered(paginationId)) { 186 var idMessage = (paginationId !== '__default') ? ' (id: ' + paginationId + ') ' : ' '; 187 throw 'pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive.'; 188 } 189 190 var paginationRange = Math.max(scope.maxSize, 5); 191 scope.pages = []; 192 scope.pagination = { 193 last: 1, 194 current: 1 195 }; 196 197 scope.$watch(function() { 198 return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId); 199 }, function(length) { 200 if (0 < length) { 201 generatePagination(); 202 } 203 }); 204 205 scope.$watch(function() { 206 return paginationService.getCurrentPage(paginationId); 207 }, function(currentPage) { 208 goToPage(currentPage); 209 }); 210 211 scope.setCurrent = function(num) { 212 if (isValidPageNumber(num)) { 213 paginationService.setCurrentPage(paginationId, num); 214 } 215 }; 216 217 function goToPage(num) { 218 if (isValidPageNumber(num)) { 219 scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); 220 scope.pagination.current = num; 221 222 // if a callback has been set, then call it with the page number as an argument 223 if (scope.onPageChange) { 224 scope.onPageChange({ newPageNumber : num }); 225 } 226 } 227 } 228 229 function generatePagination() { 230 scope.pages = generatePagesArray(1, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange); 231 scope.pagination.current = parseInt(paginationService.getCurrentPage(paginationId)); 232 scope.pagination.last = scope.pages[scope.pages.length - 1]; 233 if (scope.pagination.last < scope.pagination.current) { 234 scope.setCurrent(scope.pagination.last); 235 } 236 } 237 238 function isValidPageNumber(num) { 239 return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last)); 240 } 241 } 242 }; 243 }]); 244 245 module.filter('itemsPerPage', ['paginationService', function(paginationService) { 246 return function(collection, itemsPerPage, paginationId) { 247 if (typeof (paginationId) === 'undefined') { 248 paginationId = '__default'; 249 } 250 if (!paginationService.isRegistered(paginationId)) { 251 throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
252 } 253 var end; 254 var start; 255 if (collection instanceof Array) { 256 itemsPerPage = parseInt(itemsPerPage) || 9999999999; 257 if (paginationService.isAsyncMode(paginationId)) { 258 start = 0; 259 } else { 260 start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage; 261 } 262 end = start + itemsPerPage; 263 paginationService.setItemsPerPage(paginationId, itemsPerPage); 264 265 return collection.slice(start, end); 266 } else { 267 return collection; 268 } 269 }; 270 }]); 271 272 module.service('paginationService', function() { 273 var instances = {}; 274 var lastRegisteredInstance; 275 this.paginationDirectiveInitialized = false; 276 277 this.registerInstance = function(instanceId) { 278 if (typeof instances[instanceId] === 'undefined') { 279 instances[instanceId] = { 280 asyncMode: false 281 }; 282 lastRegisteredInstance = instanceId; 283 } 284 }; 285 286 this.isRegistered = function(instanceId) { 287 return (typeof instances[instanceId] !== 'undefined'); 288 }; 289 290 this.getLastInstanceId = function() { 291 return lastRegisteredInstance; 292 }; 293 294 this.setCurrentPageParser = function(instanceId, val, scope) { 295 instances[instanceId].currentPageParser = val; 296 instances[instanceId].context = scope; 297 }; 298 this.setCurrentPage = function(instanceId, val) { 299 instances[instanceId].currentPageParser.assign(instances[instanceId].context, val); 300 }; 301 this.getCurrentPage = function(instanceId) { 302 return instances[instanceId].currentPageParser(instances[instanceId].context); 303 }; 304 305 this.setItemsPerPage = function(instanceId, val) { 306 instances[instanceId].itemsPerPage = val; 307 }; 308 this.getItemsPerPage = function(instanceId) { 309 return instances[instanceId].itemsPerPage; 310 }; 311 312 this.setCollectionLength = function(instanceId, val) { 313 instances[instanceId].collectionLength = val; 314 }; 315 this.getCollectionLength = function(instanceId) { 316 return instances[instanceId].collectionLength; 317 }; 318 319 this.setAsyncModeTrue = function(instanceId) { 320 instances[instanceId].asyncMode = true; 321 }; 322 323 this.isAsyncMode = function(instanceId) { 324 return instances[instanceId].asyncMode; 325 }; 326 }); 327})();
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.