PageSourceSearch

http://wikigenomes.org/static/wiki/js/external_js/dirPagination.js

js wikigenomes.org collected 2026-09-25 23:42:16 UTC 27,208 bytes, 626 lines download raw bytes

1(function () {
2
3    /**
4     * Config
5     */
6    var moduleName = 'angularUtils.directives.dirPagination';
7    var DEFAULT_ID = '__default';
8
9    /**
10     * Module
11     */
12    angular.module(moduleName, [])
13        .directive('dirPaginate', ['$compile', '$parse', 'paginationService', dirPaginateDirective])
14        .directive('dirPaginateNoCompile', noCompileDirective)
15        .directive('dirPaginationControls', ['paginationService', 'paginationTemplate', dirPaginationControlsDirective])
16        .filter('itemsPerPage', ['paginationService', itemsPerPageFilter])
17        .service('paginationService', paginationService)
18        .provider('paginationTemplate', paginationTemplateProvider)
19        .run(['$templateCache', dirPaginationControlsTemplateInstaller]);
20
21    function dirPaginateDirective($compile, $parse, paginationService) {
22
23        return {
24            terminal: true,
25            multiElement: true,
26            priority: 100,
27            compile: dirPaginationCompileFn
28        };
29
30        function dirPaginationCompileFn(tElement, tAttrs
30) {
31
32            var expression = tAttrs.dirPaginate;
33            // regex taken directly from https://github.com/angular/angular.js/blob/v1.4.x/src/ng/directive/ngRepeat.js#L339
34            var match = expression.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);
35
36            var filterPattern = /\|\s*itemsPerPage\s*:\s*(.*\(\s*\w*\)|([^\)]*?(?=\s+as\s+))|[^\)]*)/;
37            if (match[2].match(filterPattern) === null) {
38                throw 'pagination directive: the \'itemsPerPage\' filter must be set.';
39            }
40            var itemsPerPageFilterRemoved = match[2].replace(filterPattern, '');
41            var collectionGetter = $parse(itemsPerPageFilterRemoved);
42
43            addNoCompileAttributes(tElement);
44
45            // If any value is specified for paginationId, we register the un-evaluated expression at this stage for the benefit of any
46            // dir-pagination-controls directives that may be looking for this ID.
47            var rawId = tAttrs.paginationId || DEFAULT_ID;
48            paginationService.registerInstance(rawId);
49
50            return function dirPaginationLinkFn(scope, element, attrs) {
51
52                // Now that we have access to the `scope` we can interpolate any expression given in the paginationId attribute and
53                // potentially register a new ID if it evaluates to a different value than the rawId.
54                var paginationId = $parse(attrs.paginationId)(scope) || attrs.paginationId || DEFAULT_ID;
55
56                // (TODO: this seems sound, but I'm reverting as many bug reports followed it's introduction in 0.11.
vendor: 24,506 bytes, lines 56-626
560.
57                // Needs more investigation.)
58                // In case rawId != paginationId we deregister using rawId for the sake of general cleanliness
59                // before registering using paginationId
60                // paginationService.deregisterInstance(rawId);
61                paginationService.registerInstance(paginationId);
62
63                var repeatExpression = getRepeatExpression(expression, paginationId);
64                addNgRepeatToElement(element, attrs, repeatExpression);
65
66                removeTemporaryAttributes(element);
67                var compiled = $compile(element);
68
69                var currentPageGetter = makeCurrentPageGetterFn(scope, attrs, paginationId);
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                    paginationService.setAsyncModeFalse(paginationId);
83                    scope.$watchCollection(function () {
84                        return collectionGetter(scope);
85                    }, function (collection) {
86                        if (collection) {
87                            var collectionLength = (collection instanceof Array) ? collection.length : Object.keys(collection).length;
88                            paginationService.setCollectionLength(paginationId, collectionLength);
89                        }
90                    });
91                }
92
93                // Delegate to the link function returned by the new compilation of the ng-repeat
94                compiled(scope);
95
96                // (TODO: Reverting this due to many bug reports in v 0.11.0. Needs investigation as the
97                // principle is sound)
98                // When the scope is destroyed, we make sure to remove the reference to it in paginationService
99                // so that it can be properly garbage collected
100                // scope.$on('$destroy', function destroyDirPagination() {
101                //     paginationService.deregisterInstance(paginationId);
102                // });
103            };
104        }
105
106        /**
107         * If a pagination id has been specified, we need to check that it is present as the second argument passed to
108         * the itemsPerPage filter. If it is not there, we add it and return the modified expression.
109         *
110         * @param expression
111         * @param paginationId
112         * @returns {*}
113         */
114        function getRepeatExpression(expression, paginationId) {
115            var repeatExpression,
116                idDefinedInFilter = !!expression.match(/(\|\s*itemsPerPage\s*:[^|]*:[^|]*)/);
117
118            if (paginationId !== DEFAULT_ID && !idDefinedInFilter) {
119                repeatExpression = expression.replace(/(\|\s*itemsPerPage\s*:\s*[^|\s]*)/, "$1 : '" + paginationId + "'");
120            } else {
121                repeatExpression = expression;
122            }
123
124            return repeatExpression;
125        }
126
127        /**
128         * Adds the ng-repeat directive to the element. In the case of multi-element (-start, -end) it adds the
129         * appropriate multi-element ng-repeat to the first and last element in the range.
130         * @param element
131         * @param attrs
132         * @param repeatExpression
133         */
134        function addNgRepeatToElement(element, attrs, repeatExpression) {
135            if (element[0].hasAttribute('dir-paginate-start') || element[0].hasAttribute('data-dir-paginate-start')) {
136                // using multiElement mode (dir-paginate-start, dir-paginate-end)
137                attrs.$set('ngRepeatStart', repeatExpression);
138                element.eq(element.length - 1).attr('ng-repeat-end', true);
139            } else {
140                attrs.$set('ngRepeat', repeatExpression);
141            }
142        }
143
144        /**
145         * Adds the dir-paginate-no-compile directive to each element in the tElement range.
146         * @param tElement
147         */
148        function addNoCompileAttributes(tElement) {
149            angular.forEach(tElement, function (el) {
150                if (el.nodeType === 1) {
151                    angular.element(el).attr('dir-paginate-no-compile', true);
152                }
153            });
154        }
155
156        /**
157         * Removes the variations on dir-paginate (data-, -start, -end) and the dir-paginate-no-compile directives.
158         * @param element
159         */
160        function removeTemporaryAttributes(element) {
161            angular.forEach(element, function (el) {
162                if (el.nodeType === 1) {
163                    angular.element(el).removeAttr('dir-paginate-no-compile');
164                }
165            });
166            element.eq(0).removeAttr('dir-paginate-start').removeAttr('dir-paginate').removeAttr('data-dir-paginate-start').removeAttr('data-dir-paginate');
167            element.eq(element.length - 1).removeAttr('dir-paginate-end').removeAttr('data-dir-paginate-end');
168        }
169
170        /**
171         * Creates a getter function for the current-page attribute, using the expression provided or a default value if
172         * no current-page expression was specified.
173         *
174         * @param scope
175         * @param attrs
176         * @param paginationId
177         * @returns {*}
178         */
179        function makeCurrentPageGetterFn(scope, attrs, paginationId) {
180            var currentPageGetter;
181            if (attrs.currentPage) {
182                currentPageGetter = $parse(attrs.currentPage);
183            } else {
184                // If the current-page attribute was not set, we'll make our own.
185                // Replace any non-alphanumeric characters which might confuse
186                // the $parse service and give unexpected results.
187                // See https://github.com/michaelbromley/angularUtils/issues/233
188                var defaultCurrentPage = (paginationId + '__currentPage').replace(/\W/g, '_');
189                scope[defaultCurrentPage] = 1;
190                currentPageGetter = $parse(defaultCurrentPage);
191            }
192            return currentPageGetter;
193        }
194    }
195
196    /**
197     * This is a helper directive that allows correct compilation when in multi-element mode (ie dir-paginate-start, dir-paginate-end).
198     * It is dynamically added to all elements in the dir-paginate compile function, and it prevents further compilation of
199     * any inner directives. It is then removed in the link function, and all inner directives are then manually compiled.
200     */
201    function noCompileDirective() {
202        return {
203            priority: 5000,
204            terminal: true
205        };
206    }
207
208    function dirPaginationControlsTemplateInstaller($templateCache) {
209        $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)">&laquo;</a></li><li ng-if="directionLinks" ng-class="{ disabled : pagination.current == 1 }"><a href="" ng-click="setCurrent(pagination.current - 1)">&lsaquo;</a></li><li ng-repeat="pageNumber in pages track by tracker(pageNumber, $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)">&rsaquo;</a></li><li ng-if="boundaryLinks"  ng-class="{ disabled : pagination.current == pagination.last }"><a href="" ng-click="setCurrent(pagination.last)">&raquo;</a></li></ul>');
210    }
211
212    function dirPaginationControlsDirective(paginationService, paginationTemplate) {
213
214        var numberRegex = /^\d+$/;
215
216        var DDO = {
217            restrict: 'AE',
218            scope: {
219                maxSize: '=?',
220                onPageChange: '&?',
221                paginationId: '=?',
222                autoHide: '=?'
223            },
224            link: dirPaginationControlsLinkFn
225        };
226
227        // We need to check the paginationTemplate service to see whether a template path or
228        // string has been specified, and add the `template` or `templateUrl` property to
229        // the DDO as appropriate. The order of priority to decide which template to use is
230        // (highest priority first):
231        // 1. paginationTemplate.getString()
232        // 2. attrs.templateUrl
233        // 3. paginationTemplate.getPath()
234        var templateString = paginationTemplate.getString();
235        if (templateString !== undefined) {
236            DDO.template = templateString;
237        } else {
238            DDO.templateUrl = function (elem, attrs) {
239                return attrs.templateUrl || paginationTemplate.getPath();
240            };
241        }
242        return DDO;
243
244        function dirPaginationControlsLinkFn(scope, element, attrs) {
245
246            // rawId is the un-interpolated value of the pagination-id attribute. This is only important when the corresponding dir-paginate directive has
247            // 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
248            // no corresponding dir-paginate directive and wrongly throwing an exception.
249            var rawId = attrs.paginationId || DEFAULT_ID;
250            var paginationId = scope.paginationId || attrs.paginationId || DEFAULT_ID;
251
252            if (!paginationService.isRegistered(paginationId) && !paginationService.isRegistered(rawId)) {
253                var idMessage = (paginationId !== DEFAULT_ID) ? ' (id: ' + paginationId + ') ' : ' ';
254                if (window.console) {
255                    console.warn('Pagination directive: the pagination controls' + idMessage + 'cannot be used without the corresponding pagination directive, which was not found at link time.');
256                }
257            }
258
259            if (!scope.maxSize) {
260                scope.maxSize = 9;
261            }
262            scope.autoHide = scope.autoHide === undefined ? true : scope.autoHide;
263            scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : true;
264            scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : false;
265
266            var paginationRange = Math.max(scope.maxSize, 5);
267            scope.pages = [];
268            scope.pagination = {
269                last: 1,
270                current: 1
271            };
272            scope.range = {
273                lower: 1,
274                upper: 1,
275                total: 1
276            };
277
278            scope.$watch('maxSize', function (val) {
279                if (val) {
280                    paginationRange = Math.max(scope.maxSize, 5);
281                    generatePagination();
282                }
283            });
284
285            scope.$watch(function () {
286                if (paginationService.isRegistered(paginationId)) {
287                    return (paginationService.getCollectionLength(paginationId) + 1) * paginationService.getItemsPerPage(paginationId);
288                }
289            }, function (length) {
290                if (0 < length) {
291                    generatePagination();
292                }
293            });
294
295            scope.$watch(function () {
296                if (paginationService.isRegistered(paginationId)) {
297                    return (paginationService.getItemsPerPage(paginationId));
298                }
299            }, function (current, previous) {
300                if (current != previous && typeof previous !== 'undefined') {
301                    goToPage(scope.pagination.current);
302                }
303            });
304
305            scope.$watch(function () {
306                if (paginationService.isRegistered(paginationId)) {
307                    return paginationService.getCurrentPage(paginationId);
308                }
309            }, function (currentPage, previousPage) {
310                if (currentPage != previousPage) {
311                    goToPage(currentPage);
312                }
313            });
314
315            scope.setCurrent = function (num) {
316                if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) {
317                    num = parseInt(num, 10);
318                    paginationService.setCurrentPage(paginationId, num);
319                }
320            };
321
322            /**
323             * Custom "track by" function which allows for duplicate "..." entries on long lists,
324             * yet fixes the problem of wrongly-highlighted links which happens when using
325             * "track by $index" - see https://github.com/michaelbromley/angularUtils/issues/153
326             * @param id
327             * @param index
328             * @returns {string}
329             */
330            scope.tracker = function (id, index) {
331                return id + '_' + index;
332            };
333
334            function goToPage(num) {
335                if (paginationService.isRegistered(paginationId) && isValidPageNumber(num)) {
336                    var oldPageNumber = scope.pagination.current;
337
338                    scope.pages = generatePagesArray(num, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
339                    scope.pagination.current = num;
340                    updateRangeValues();
341
342                    // if a callback has been set, then call it with the page number as the first argument
343                    // and the previous page number as a second argument
344                    if (scope.onPageChange) {
345                        scope.onPageChange({
346                            newPageNumber: num,
347                            oldPageNumber: oldPageNumber
348                        });
349                    }
350                }
351            }
352
353            function generatePagination() {
354                if (paginationService.isRegistered(paginationId)) {
355                    var page = parseInt(paginationService.getCurrentPage(paginationId)) || 1;
356                    scope.pages = generatePagesArray(page, paginationService.getCollectionLength(paginationId), paginationService.getItemsPerPage(paginationId), paginationRange);
357                    scope.pagination.current = page;
358                    scope.pagination.last = scope.pages[scope.pages.length - 1];
359                    if (scope.pagination.last < scope.pagination.current) {
360                        scope.setCurrent(scope.pagination.last);
361                    } else {
362                        updateRangeValues();
363                    }
364                }
365            }
366
367            /**
368             * This function updates the values (lower, upper, total) of the `scope.range` object, which can be used in the pagination
369             * template to display the current page range, e.g. "showing 21 - 40 of 144 results";
370             */
371            function updateRangeValues() {
372                if (paginationService.isRegistered(paginationId)) {
373                    var currentPage = paginationService.getCurrentPage(paginationId),
374                        itemsPerPage = paginationService.getItemsPerPage(paginationId),
375                        totalItems = paginationService.getCollectionLength(paginationId);
376
377                    scope.range.lower = (currentPage - 1) * itemsPerPage + 1;
378                    scope.range.upper = Math.min(currentPage * itemsPerPage, totalItems);
379                    scope.range.total = totalItems;
380                }
381            }
382
383            function isValidPageNumber(num) {
384                return (numberRegex.test(num) && (0 < num && num <= scope.pagination.last));
385            }
386        }
387
388        /**
389         * Generate an array of page numbers (or the '...' string) which is used in an ng-repeat to generate the
390         * links used in pagination
391         *
392         * @param currentPage
393         * @param rowsPerPage
394         * @param paginationRange
395         * @param collectionLength
396         * @returns {Array}
397         */
398        function generatePagesArray(currentPage, collectionLength, rowsPerPage, paginationRange) {
399            var pages = [];
400            var totalPages = Math.ceil(collectionLength / rowsPerPage);
401            var halfWay = Math.ceil(paginationRange / 2);
402            var position;
403
404            if (currentPage <= halfWay) {
405                position = 'start';
406            } else if (totalPages - halfWay < currentPage) {
407                position = 'end';
408            } else {
409                position = 'middle';
410            }
411
412            var ellipsesNeeded = paginationRange < totalPages;
413            var i = 1;
414            while (i <= totalPages && i <= paginationRange) {
415                var pageNumber = calculatePageNumber(i, currentPage, paginationRange, totalPages);
416
417                var openingEllipsesNeeded = (i === 2 && (position === 'middle' || position === 'end'));
418                var closingEllipsesNeeded = (i === paginationRange - 1 && (position === 'middle' || position === 'start'));
419                if (ellipsesNeeded && (openingEllipsesNeeded || closingEllipsesNeeded)) {
420                    pages.push('...');
421                } else {
422                    pages.push(pageNumber);
423                }
424                i++;
425            }
426            return pages;
427        }
428
429        /**
430         * Given the position in the sequence of pagination links [i], figure out what page number corresponds to that position.
431         *
432         * @param i
433         * @param currentPage
434         * @param paginationRange
435         * @param totalPages
436         * @returns {*}
437         */
438        function calculatePageNumber(i, currentPage, paginationRange, totalPages) {
439            var halfWay = Math.ceil(paginationRange / 2);
440            if (i === paginationRange) {
441                return totalPages;
442            } else if (i === 1) {
443                return i;
444            } else if (paginationRange < totalPages) {
445                if (totalPages - halfWay < currentPage) {
446                    return totalPages - paginationRange + i;
447                } else if (halfWay < currentPage) {
448                    return currentPage - halfWay + i;
449                } else {
450                    return i;
451                }
452            } else {
453                return i;
454            }
455        }
456    }
457
458    /**
459     * This filter slices the collection into pages based on the current page number and number of items per page.
460     * @param paginationService
461     * @returns {Function}
462     */
463    function itemsPerPageFilter(paginationService) {
464
465        return function (collection, itemsPerPage, paginationId) {
466            if (typeof (paginationId) === 'undefined') {
467                paginationId = DEFAULT_ID;
468            }
469            if (!paginationService.isRegistered(paginationId)) {
470                throw 'pagination directive: the itemsPerPage id argument (id: ' + paginationId + ') does not match a registered pagination-id.';
471            }
472            var end;
473            var start;
474            if (angular.isObject(collection)) {
475                itemsPerPage = parseInt(itemsPerPage) || 9999999999;
476                if (paginationService.isAsyncMode(paginationId)) {
477                    start = 0;
478                } else {
479                    start = (paginationService.getCurrentPage(paginationId) - 1) * itemsPerPage;
480                }
481                end = start + itemsPerPage;
482                paginationService.setItemsPerPage(paginationId, itemsPerPage);
483
484                if (collection instanceof Array) {
485                    // the array just needs to be sliced
486                    return collection.slice(start, end);
487                } else {
488                    // in the case of an object, we need to get an array of keys, slice that, then map back to
489                    // the original object.
490                    var slicedObject = {};
491                    angular.forEach(keys(collection).slice(start, end), function (key) {
492                        slicedObject[key] = collection[key];
493                    });
494                    return slicedObject;
495                }
496            } else {
497                return collection;
498            }
499        };
500    }
501
502    /**
503     * Shim for the Object.keys() method which does not exist in IE < 9
504     * @param obj
505     * @returns {Array}
506     */
507    function keys(obj) {
508        if (!Object.keys) {
509            var objKeys = [];
510            for (var i in obj) {
511                if (obj.hasOwnProperty(i)) {
512                    objKeys.push(i);
513                }
514            }
515            return objKeys;
516        } else {
517            return Object.keys(obj);
518        }
519    }
520
521    /**
522     * This service allows the various parts of the module to communicate and stay in sync.
523     */
524    function paginationService() {
525
526        var instances = {};
527        var lastRegisteredInstance;
528
529        this.registerInstance = function (instanceId) {
530            if (typeof instances[instanceId] === 'undefined') {
531                instances[instanceId] = {
532                    asyncMode: false
533                };
534                lastRegisteredInstance = instanceId;
535            }
536        };
537
538        this.deregisterInstance = function (instanceId) {
539            delete instances[instanceId];
540        };
541
542        this.isRegistered = function (instanceId) {
543            return (typeof instances[instanceId] !== 'undefined');
544        };
545
546        this.getLastInstanceId = function () {
547            return lastRegisteredInstance;
548        };
549
550        this.setCurrentPageParser = function (instanceId, val, scope) {
551            instances[instanceId].currentPageParser = val;
552            instances[instanceId].context = scope;
553        };
554        this.setCurrentPage = function (instanceId, val) {
555            instances[instanceId].currentPageParser.assign(instances[instanceId].context, val);
556        };
557        this.getCurrentPage = function (instanceId) {
558            var parser = instances[instanceId].currentPageParser;
559            return parser ? parser(instances[instanceId].context) : 1;
560        };
561
562        this.setItemsPerPage = function (instanceId, val) {
563            instances[instanceId].itemsPerPage = val;
564        };
565        this.getItemsPerPage = function (instanceId) {
566            return instances[instanceId].itemsPerPage;
567        };
568
569        this.setCollectionLength = function (instanceId, val) {
570            instances[instanceId].collectionLength = val;
571        };
572        this.getCollectionLength = function (instanceId) {
573            return instances[instanceId].collectionLength;
574        };
575
576        this.setAsyncModeTrue = function (instanceId) {
577            instances[instanceId].asyncMode = true;
578        };
579
580        this.setAsyncModeFalse = function (instanceId) {
581            instances[instanceId].asyncMode = false;
582        };
583
584        this.isAsyncMode = function (instanceId) {
585            return instances[instanceId].asyncMode;
586        };
587    }
588
589    /**
590     * This provider allows global configuration of the template path used by the dir-pagination-controls directive.
591     */
592    function paginationTemplateProvider() {
593
594        var templatePath = 'angularUtils.directives.dirPagination.template';
595        var templateString;
596
597        /**
598         * Set a templateUrl to be used by all instances of <dir-pagination-controls>
599         * @param {String} path
600         */
601        this.setPath = function (path) {
602            templatePath = path;
603        };
604
605        /**
606         * Set a string of HTML to be used as a template by all instances
607         * of <dir-pagination-controls>. If both a path *and* a string have been set,
608         * the string takes precedence.
609         * @param {String} str
610         */
611        this.setString = function (str) {
612            templateString = str;
613        };
614
615        this.$get = function () {
616            return {
617                getPath: function () {
618                    return templatePath;
619                },
620                getString: function () {
621                    return templateString;
622                }
623            };
624        };
625    }
626})();

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.