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