blob: 67838936729d5defd5712c6d58122149e7559d5e [file] [log] [blame]
Ed Tanous904063f2017-03-02 16:48:24 -08001/*
2 * angular-ui-bootstrap
3 * http://angular-ui.github.io/bootstrap/
4
Ed Tanous4758d5b2017-06-06 15:28:13 -07005 * Version: 2.5.0 - 2017-01-28
Ed Tanous904063f2017-03-02 16:48:24 -08006 * License: MIT
Ed Tanous4758d5b2017-06-06 15:28:13 -07007 */angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.tabindex","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.multiMap","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
Ed Tanous904063f2017-03-02 16:48:24 -08008angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/datepickerPopup/popup.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]);
9angular.module('ui.bootstrap.collapse', [])
10
11 .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
12 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
13 return {
14 link: function(scope, element, attrs) {
15 var expandingExpr = $parse(attrs.expanding),
16 expandedExpr = $parse(attrs.expanded),
17 collapsingExpr = $parse(attrs.collapsing),
18 collapsedExpr = $parse(attrs.collapsed),
19 horizontal = false,
20 css = {},
21 cssTo = {};
22
23 init();
24
25 function init() {
26 horizontal = !!('horizontal' in attrs);
27 if (horizontal) {
28 css = {
29 width: ''
30 };
31 cssTo = {width: '0'};
32 } else {
33 css = {
34 height: ''
35 };
36 cssTo = {height: '0'};
37 }
38 if (!scope.$eval(attrs.uibCollapse)) {
39 element.addClass('in')
40 .addClass('collapse')
41 .attr('aria-expanded', true)
42 .attr('aria-hidden', false)
43 .css(css);
44 }
45 }
46
47 function getScrollFromElement(element) {
48 if (horizontal) {
49 return {width: element.scrollWidth + 'px'};
50 }
51 return {height: element.scrollHeight + 'px'};
52 }
53
54 function expand() {
55 if (element.hasClass('collapse') && element.hasClass('in')) {
56 return;
57 }
58
59 $q.resolve(expandingExpr(scope))
60 .then(function() {
61 element.removeClass('collapse')
62 .addClass('collapsing')
63 .attr('aria-expanded', true)
64 .attr('aria-hidden', false);
65
66 if ($animateCss) {
67 $animateCss(element, {
68 addClass: 'in',
69 easing: 'ease',
70 css: {
71 overflow: 'hidden'
72 },
73 to: getScrollFromElement(element[0])
74 }).start()['finally'](expandDone);
75 } else {
76 $animate.addClass(element, 'in', {
77 css: {
78 overflow: 'hidden'
79 },
80 to: getScrollFromElement(element[0])
81 }).then(expandDone);
82 }
Ed Tanous4758d5b2017-06-06 15:28:13 -070083 }, angular.noop);
Ed Tanous904063f2017-03-02 16:48:24 -080084 }
85
86 function expandDone() {
87 element.removeClass('collapsing')
88 .addClass('collapse')
89 .css(css);
90 expandedExpr(scope);
91 }
92
93 function collapse() {
94 if (!element.hasClass('collapse') && !element.hasClass('in')) {
95 return collapseDone();
96 }
97
98 $q.resolve(collapsingExpr(scope))
99 .then(function() {
100 element
101 // IMPORTANT: The width must be set before adding "collapsing" class.
102 // Otherwise, the browser attempts to animate from width 0 (in
103 // collapsing class) to the given width here.
104 .css(getScrollFromElement(element[0]))
105 // initially all panel collapse have the collapse class, this removal
106 // prevents the animation from jumping to collapsed state
107 .removeClass('collapse')
108 .addClass('collapsing')
109 .attr('aria-expanded', false)
110 .attr('aria-hidden', true);
111
112 if ($animateCss) {
113 $animateCss(element, {
114 removeClass: 'in',
115 to: cssTo
116 }).start()['finally'](collapseDone);
117 } else {
118 $animate.removeClass(element, 'in', {
119 to: cssTo
120 }).then(collapseDone);
121 }
Ed Tanous4758d5b2017-06-06 15:28:13 -0700122 }, angular.noop);
Ed Tanous904063f2017-03-02 16:48:24 -0800123 }
124
125 function collapseDone() {
126 element.css(cssTo); // Required so that collapse works when animation is disabled
127 element.removeClass('collapsing')
128 .addClass('collapse');
129 collapsedExpr(scope);
130 }
131
132 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
133 if (shouldCollapse) {
134 collapse();
135 } else {
136 expand();
137 }
138 });
139 }
140 };
141 }]);
142
143angular.module('ui.bootstrap.tabindex', [])
144
145.directive('uibTabindexToggle', function() {
146 return {
147 restrict: 'A',
148 link: function(scope, elem, attrs) {
149 attrs.$observe('disabled', function(disabled) {
150 attrs.$set('tabindex', disabled ? -1 : null);
151 });
152 }
153 };
154});
155
156angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse', 'ui.bootstrap.tabindex'])
157
158.constant('uibAccordionConfig', {
159 closeOthers: true
160})
161
162.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
163 // This array keeps track of the accordion groups
164 this.groups = [];
165
166 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
167 this.closeOthers = function(openGroup) {
168 var closeOthers = angular.isDefined($attrs.closeOthers) ?
169 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
170 if (closeOthers) {
171 angular.forEach(this.groups, function(group) {
172 if (group !== openGroup) {
173 group.isOpen = false;
174 }
175 });
176 }
177 };
178
179 // This is called from the accordion-group directive to add itself to the accordion
180 this.addGroup = function(groupScope) {
181 var that = this;
182 this.groups.push(groupScope);
183
184 groupScope.$on('$destroy', function(event) {
185 that.removeGroup(groupScope);
186 });
187 };
188
189 // This is called from the accordion-group directive when to remove itself
190 this.removeGroup = function(group) {
191 var index = this.groups.indexOf(group);
192 if (index !== -1) {
193 this.groups.splice(index, 1);
194 }
195 };
196}])
197
198// The accordion directive simply sets up the directive controller
199// and adds an accordion CSS class to itself element.
200.directive('uibAccordion', function() {
201 return {
202 controller: 'UibAccordionController',
203 controllerAs: 'accordion',
204 transclude: true,
205 templateUrl: function(element, attrs) {
206 return attrs.templateUrl || 'uib/template/accordion/accordion.html';
207 }
208 };
209})
210
211// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
212.directive('uibAccordionGroup', function() {
213 return {
214 require: '^uibAccordion', // We need this directive to be inside an accordion
215 transclude: true, // It transcludes the contents of the directive into the template
216 restrict: 'A',
217 templateUrl: function(element, attrs) {
218 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
219 },
220 scope: {
221 heading: '@', // Interpolate the heading attribute onto this scope
222 panelClass: '@?', // Ditto with panelClass
223 isOpen: '=?',
224 isDisabled: '=?'
225 },
226 controller: function() {
227 this.setHeading = function(element) {
228 this.heading = element;
229 };
230 },
231 link: function(scope, element, attrs, accordionCtrl) {
232 element.addClass('panel');
233 accordionCtrl.addGroup(scope);
234
235 scope.openClass = attrs.openClass || 'panel-open';
236 scope.panelClass = attrs.panelClass || 'panel-default';
237 scope.$watch('isOpen', function(value) {
238 element.toggleClass(scope.openClass, !!value);
239 if (value) {
240 accordionCtrl.closeOthers(scope);
241 }
242 });
243
244 scope.toggleOpen = function($event) {
245 if (!scope.isDisabled) {
246 if (!$event || $event.which === 32) {
247 scope.isOpen = !scope.isOpen;
248 }
249 }
250 };
251
252 var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
253 scope.headingId = id + '-tab';
254 scope.panelId = id + '-panel';
255 }
256 };
257})
258
259// Use accordion-heading below an accordion-group to provide a heading containing HTML
260.directive('uibAccordionHeading', function() {
261 return {
262 transclude: true, // Grab the contents to be used as the heading
263 template: '', // In effect remove this element!
264 replace: true,
265 require: '^uibAccordionGroup',
266 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
267 // Pass the heading to the accordion-group controller
268 // so that it can be transcluded into the right place in the template
269 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
270 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
271 }
272 };
273})
274
275// Use in the accordion-group template to indicate where you want the heading to be transcluded
276// You must provide the property on the accordion-group controller that will hold the transcluded element
277.directive('uibAccordionTransclude', function() {
278 return {
279 require: '^uibAccordionGroup',
280 link: function(scope, element, attrs, controller) {
281 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
282 if (heading) {
283 var elem = angular.element(element[0].querySelector(getHeaderSelectors()));
284 elem.html('');
285 elem.append(heading);
286 }
287 });
288 }
289 };
290
291 function getHeaderSelectors() {
292 return 'uib-accordion-header,' +
293 'data-uib-accordion-header,' +
294 'x-uib-accordion-header,' +
295 'uib\\:accordion-header,' +
296 '[uib-accordion-header],' +
297 '[data-uib-accordion-header],' +
298 '[x-uib-accordion-header]';
299 }
300});
301
302angular.module('ui.bootstrap.alert', [])
303
304.controller('UibAlertController', ['$scope', '$element', '$attrs', '$interpolate', '$timeout', function($scope, $element, $attrs, $interpolate, $timeout) {
305 $scope.closeable = !!$attrs.close;
306 $element.addClass('alert');
307 $attrs.$set('role', 'alert');
308 if ($scope.closeable) {
309 $element.addClass('alert-dismissible');
310 }
311
312 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
313 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
314
315 if (dismissOnTimeout) {
316 $timeout(function() {
317 $scope.close();
318 }, parseInt(dismissOnTimeout, 10));
319 }
320}])
321
322.directive('uibAlert', function() {
323 return {
324 controller: 'UibAlertController',
325 controllerAs: 'alert',
326 restrict: 'A',
327 templateUrl: function(element, attrs) {
328 return attrs.templateUrl || 'uib/template/alert/alert.html';
329 },
330 transclude: true,
331 scope: {
332 close: '&'
333 }
334 };
335});
336
337angular.module('ui.bootstrap.buttons', [])
338
339.constant('uibButtonConfig', {
340 activeClass: 'active',
341 toggleEvent: 'click'
342})
343
344.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
345 this.activeClass = buttonConfig.activeClass || 'active';
346 this.toggleEvent = buttonConfig.toggleEvent || 'click';
347}])
348
349.directive('uibBtnRadio', ['$parse', function($parse) {
350 return {
351 require: ['uibBtnRadio', 'ngModel'],
352 controller: 'UibButtonsController',
353 controllerAs: 'buttons',
354 link: function(scope, element, attrs, ctrls) {
355 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
356 var uncheckableExpr = $parse(attrs.uibUncheckable);
357
358 element.find('input').css({display: 'none'});
359
360 //model -> UI
361 ngModelCtrl.$render = function() {
362 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
363 };
364
365 //ui->model
366 element.on(buttonsCtrl.toggleEvent, function() {
367 if (attrs.disabled) {
368 return;
369 }
370
371 var isActive = element.hasClass(buttonsCtrl.activeClass);
372
373 if (!isActive || angular.isDefined(attrs.uncheckable)) {
374 scope.$apply(function() {
375 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
376 ngModelCtrl.$render();
377 });
378 }
379 });
380
381 if (attrs.uibUncheckable) {
382 scope.$watch(uncheckableExpr, function(uncheckable) {
383 attrs.$set('uncheckable', uncheckable ? '' : undefined);
384 });
385 }
386 }
387 };
388}])
389
390.directive('uibBtnCheckbox', function() {
391 return {
392 require: ['uibBtnCheckbox', 'ngModel'],
393 controller: 'UibButtonsController',
394 controllerAs: 'button',
395 link: function(scope, element, attrs, ctrls) {
396 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
397
398 element.find('input').css({display: 'none'});
399
400 function getTrueValue() {
401 return getCheckboxValue(attrs.btnCheckboxTrue, true);
402 }
403
404 function getFalseValue() {
405 return getCheckboxValue(attrs.btnCheckboxFalse, false);
406 }
407
408 function getCheckboxValue(attribute, defaultValue) {
409 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
410 }
411
412 //model -> UI
413 ngModelCtrl.$render = function() {
414 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
415 };
416
417 //ui->model
418 element.on(buttonsCtrl.toggleEvent, function() {
419 if (attrs.disabled) {
420 return;
421 }
422
423 scope.$apply(function() {
424 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
425 ngModelCtrl.$render();
426 });
427 });
428 }
429 };
430});
431
432angular.module('ui.bootstrap.carousel', [])
433
434.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
435 var self = this,
436 slides = self.slides = $scope.slides = [],
437 SLIDE_DIRECTION = 'uib-slideDirection',
438 currentIndex = $scope.active,
Ed Tanous4758d5b2017-06-06 15:28:13 -0700439 currentInterval, isPlaying;
Ed Tanous904063f2017-03-02 16:48:24 -0800440
441 var destroyed = false;
442 $element.addClass('carousel');
443
444 self.addSlide = function(slide, element) {
445 slides.push({
446 slide: slide,
447 element: element
448 });
449 slides.sort(function(a, b) {
450 return +a.slide.index - +b.slide.index;
451 });
452 //if this is the first slide or the slide is set to active, select it
453 if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
454 if ($scope.$currentTransition) {
455 $scope.$currentTransition = null;
456 }
457
458 currentIndex = slide.index;
459 $scope.active = slide.index;
460 setActive(currentIndex);
461 self.select(slides[findSlideIndex(slide)]);
462 if (slides.length === 1) {
463 $scope.play();
464 }
465 }
466 };
467
468 self.getCurrentIndex = function() {
469 for (var i = 0; i < slides.length; i++) {
470 if (slides[i].slide.index === currentIndex) {
471 return i;
472 }
473 }
474 };
475
476 self.next = $scope.next = function() {
477 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
478
479 if (newIndex === 0 && $scope.noWrap()) {
480 $scope.pause();
481 return;
482 }
483
484 return self.select(slides[newIndex], 'next');
485 };
486
487 self.prev = $scope.prev = function() {
488 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
489
490 if ($scope.noWrap() && newIndex === slides.length - 1) {
491 $scope.pause();
492 return;
493 }
494
495 return self.select(slides[newIndex], 'prev');
496 };
497
498 self.removeSlide = function(slide) {
499 var index = findSlideIndex(slide);
500
Ed Tanous904063f2017-03-02 16:48:24 -0800501 //get the index of the slide inside the carousel
502 slides.splice(index, 1);
503 if (slides.length > 0 && currentIndex === index) {
504 if (index >= slides.length) {
505 currentIndex = slides.length - 1;
506 $scope.active = currentIndex;
507 setActive(currentIndex);
508 self.select(slides[slides.length - 1]);
509 } else {
510 currentIndex = index;
511 $scope.active = currentIndex;
512 setActive(currentIndex);
513 self.select(slides[index]);
514 }
515 } else if (currentIndex > index) {
516 currentIndex--;
517 $scope.active = currentIndex;
518 }
519
520 //clean the active value when no more slide
521 if (slides.length === 0) {
522 currentIndex = null;
523 $scope.active = null;
Ed Tanous904063f2017-03-02 16:48:24 -0800524 }
525 };
526
527 /* direction: "prev" or "next" */
528 self.select = $scope.select = function(nextSlide, direction) {
529 var nextIndex = findSlideIndex(nextSlide.slide);
530 //Decide direction if it's not given
531 if (direction === undefined) {
532 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
533 }
534 //Prevent this user-triggered transition from occurring if there is already one in progress
535 if (nextSlide.slide.index !== currentIndex &&
536 !$scope.$currentTransition) {
537 goNext(nextSlide.slide, nextIndex, direction);
Ed Tanous904063f2017-03-02 16:48:24 -0800538 }
539 };
540
541 /* Allow outside people to call indexOf on slides array */
542 $scope.indexOfSlide = function(slide) {
543 return +slide.slide.index;
544 };
545
546 $scope.isActive = function(slide) {
547 return $scope.active === slide.slide.index;
548 };
549
550 $scope.isPrevDisabled = function() {
551 return $scope.active === 0 && $scope.noWrap();
552 };
553
554 $scope.isNextDisabled = function() {
555 return $scope.active === slides.length - 1 && $scope.noWrap();
556 };
557
558 $scope.pause = function() {
559 if (!$scope.noPause) {
560 isPlaying = false;
561 resetTimer();
562 }
563 };
564
565 $scope.play = function() {
566 if (!isPlaying) {
567 isPlaying = true;
568 restartTimer();
569 }
570 };
571
572 $element.on('mouseenter', $scope.pause);
573 $element.on('mouseleave', $scope.play);
574
575 $scope.$on('$destroy', function() {
576 destroyed = true;
577 resetTimer();
578 });
579
580 $scope.$watch('noTransition', function(noTransition) {
581 $animate.enabled($element, !noTransition);
582 });
583
584 $scope.$watch('interval', restartTimer);
585
586 $scope.$watchCollection('slides', resetTransition);
587
588 $scope.$watch('active', function(index) {
589 if (angular.isNumber(index) && currentIndex !== index) {
590 for (var i = 0; i < slides.length; i++) {
591 if (slides[i].slide.index === index) {
592 index = i;
593 break;
594 }
595 }
596
597 var slide = slides[index];
598 if (slide) {
599 setActive(index);
600 self.select(slides[index]);
601 currentIndex = index;
602 }
603 }
604 });
605
Ed Tanous904063f2017-03-02 16:48:24 -0800606 function getSlideByIndex(index) {
607 for (var i = 0, l = slides.length; i < l; ++i) {
608 if (slides[i].index === index) {
609 return slides[i];
610 }
611 }
612 }
613
614 function setActive(index) {
615 for (var i = 0; i < slides.length; i++) {
616 slides[i].slide.active = i === index;
617 }
618 }
619
620 function goNext(slide, index, direction) {
621 if (destroyed) {
622 return;
623 }
624
625 angular.extend(slide, {direction: direction});
626 angular.extend(slides[currentIndex].slide || {}, {direction: direction});
627 if ($animate.enabled($element) && !$scope.$currentTransition &&
628 slides[index].element && self.slides.length > 1) {
629 slides[index].element.data(SLIDE_DIRECTION, slide.direction);
630 var currentIdx = self.getCurrentIndex();
631
632 if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
633 slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
634 }
635
636 $scope.$currentTransition = true;
637 $animate.on('addClass', slides[index].element, function(element, phase) {
638 if (phase === 'close') {
639 $scope.$currentTransition = null;
640 $animate.off('addClass', element);
Ed Tanous904063f2017-03-02 16:48:24 -0800641 }
642 });
643 }
644
645 $scope.active = slide.index;
646 currentIndex = slide.index;
647 setActive(index);
648
649 //every time you change slides, reset the timer
650 restartTimer();
651 }
652
653 function findSlideIndex(slide) {
654 for (var i = 0; i < slides.length; i++) {
655 if (slides[i].slide === slide) {
656 return i;
657 }
658 }
659 }
660
661 function resetTimer() {
662 if (currentInterval) {
663 $interval.cancel(currentInterval);
664 currentInterval = null;
665 }
666 }
667
668 function resetTransition(slides) {
669 if (!slides.length) {
670 $scope.$currentTransition = null;
Ed Tanous904063f2017-03-02 16:48:24 -0800671 }
672 }
673
674 function restartTimer() {
675 resetTimer();
676 var interval = +$scope.interval;
677 if (!isNaN(interval) && interval > 0) {
678 currentInterval = $interval(timerFn, interval);
679 }
680 }
681
682 function timerFn() {
683 var interval = +$scope.interval;
684 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
685 $scope.next();
686 } else {
687 $scope.pause();
688 }
689 }
690}])
691
692.directive('uibCarousel', function() {
693 return {
694 transclude: true,
695 controller: 'UibCarouselController',
696 controllerAs: 'carousel',
697 restrict: 'A',
698 templateUrl: function(element, attrs) {
699 return attrs.templateUrl || 'uib/template/carousel/carousel.html';
700 },
701 scope: {
702 active: '=',
703 interval: '=',
704 noTransition: '=',
705 noPause: '=',
706 noWrap: '&'
707 }
708 };
709})
710
711.directive('uibSlide', ['$animate', function($animate) {
712 return {
713 require: '^uibCarousel',
714 restrict: 'A',
715 transclude: true,
716 templateUrl: function(element, attrs) {
717 return attrs.templateUrl || 'uib/template/carousel/slide.html';
718 },
719 scope: {
720 actual: '=?',
721 index: '=?'
722 },
723 link: function (scope, element, attrs, carouselCtrl) {
724 element.addClass('item');
725 carouselCtrl.addSlide(scope, element);
726 //when the scope is destroyed then remove the slide from the current slides array
727 scope.$on('$destroy', function() {
728 carouselCtrl.removeSlide(scope);
729 });
730
731 scope.$watch('active', function(active) {
732 $animate[active ? 'addClass' : 'removeClass'](element, 'active');
733 });
734 }
735 };
736}])
737
738.animation('.item', ['$animateCss',
739function($animateCss) {
740 var SLIDE_DIRECTION = 'uib-slideDirection';
741
742 function removeClass(element, className, callback) {
743 element.removeClass(className);
744 if (callback) {
745 callback();
746 }
747 }
748
749 return {
750 beforeAddClass: function(element, className, done) {
751 if (className === 'active') {
752 var stopped = false;
753 var direction = element.data(SLIDE_DIRECTION);
754 var directionClass = direction === 'next' ? 'left' : 'right';
755 var removeClassFn = removeClass.bind(this, element,
756 directionClass + ' ' + direction, done);
757 element.addClass(direction);
758
759 $animateCss(element, {addClass: directionClass})
760 .start()
761 .done(removeClassFn);
762
763 return function() {
764 stopped = true;
765 };
766 }
767 done();
768 },
769 beforeRemoveClass: function (element, className, done) {
770 if (className === 'active') {
771 var stopped = false;
772 var direction = element.data(SLIDE_DIRECTION);
773 var directionClass = direction === 'next' ? 'left' : 'right';
774 var removeClassFn = removeClass.bind(this, element, directionClass, done);
775
776 $animateCss(element, {addClass: directionClass})
777 .start()
778 .done(removeClassFn);
779
780 return function() {
781 stopped = true;
782 };
783 }
784 done();
785 }
786 };
787}]);
788
789angular.module('ui.bootstrap.dateparser', [])
790
Ed Tanous4758d5b2017-06-06 15:28:13 -0700791.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', 'filterFilter', function($log, $locale, dateFilter, orderByFilter, filterFilter) {
Ed Tanous904063f2017-03-02 16:48:24 -0800792 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
793 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
794
795 var localeId;
796 var formatCodeToRegex;
797
798 this.init = function() {
799 localeId = $locale.id;
800
801 this.parsers = {};
802 this.formatters = {};
803
804 formatCodeToRegex = [
805 {
806 key: 'yyyy',
807 regex: '\\d{4}',
808 apply: function(value) { this.year = +value; },
809 formatter: function(date) {
810 var _date = new Date();
811 _date.setFullYear(Math.abs(date.getFullYear()));
812 return dateFilter(_date, 'yyyy');
813 }
814 },
815 {
816 key: 'yy',
817 regex: '\\d{2}',
818 apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
819 formatter: function(date) {
820 var _date = new Date();
821 _date.setFullYear(Math.abs(date.getFullYear()));
822 return dateFilter(_date, 'yy');
823 }
824 },
825 {
826 key: 'y',
827 regex: '\\d{1,4}',
828 apply: function(value) { this.year = +value; },
829 formatter: function(date) {
830 var _date = new Date();
831 _date.setFullYear(Math.abs(date.getFullYear()));
832 return dateFilter(_date, 'y');
833 }
834 },
835 {
836 key: 'M!',
837 regex: '0?[1-9]|1[0-2]',
838 apply: function(value) { this.month = value - 1; },
839 formatter: function(date) {
840 var value = date.getMonth();
841 if (/^[0-9]$/.test(value)) {
842 return dateFilter(date, 'MM');
843 }
844
845 return dateFilter(date, 'M');
846 }
847 },
848 {
849 key: 'MMMM',
850 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
851 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
852 formatter: function(date) { return dateFilter(date, 'MMMM'); }
853 },
854 {
855 key: 'MMM',
856 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
857 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
858 formatter: function(date) { return dateFilter(date, 'MMM'); }
859 },
860 {
861 key: 'MM',
862 regex: '0[1-9]|1[0-2]',
863 apply: function(value) { this.month = value - 1; },
864 formatter: function(date) { return dateFilter(date, 'MM'); }
865 },
866 {
867 key: 'M',
868 regex: '[1-9]|1[0-2]',
869 apply: function(value) { this.month = value - 1; },
870 formatter: function(date) { return dateFilter(date, 'M'); }
871 },
872 {
873 key: 'd!',
874 regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
875 apply: function(value) { this.date = +value; },
876 formatter: function(date) {
877 var value = date.getDate();
878 if (/^[1-9]$/.test(value)) {
879 return dateFilter(date, 'dd');
880 }
881
882 return dateFilter(date, 'd');
883 }
884 },
885 {
886 key: 'dd',
887 regex: '[0-2][0-9]{1}|3[0-1]{1}',
888 apply: function(value) { this.date = +value; },
889 formatter: function(date) { return dateFilter(date, 'dd'); }
890 },
891 {
892 key: 'd',
893 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
894 apply: function(value) { this.date = +value; },
895 formatter: function(date) { return dateFilter(date, 'd'); }
896 },
897 {
898 key: 'EEEE',
899 regex: $locale.DATETIME_FORMATS.DAY.join('|'),
900 formatter: function(date) { return dateFilter(date, 'EEEE'); }
901 },
902 {
903 key: 'EEE',
904 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
905 formatter: function(date) { return dateFilter(date, 'EEE'); }
906 },
907 {
908 key: 'HH',
909 regex: '(?:0|1)[0-9]|2[0-3]',
910 apply: function(value) { this.hours = +value; },
911 formatter: function(date) { return dateFilter(date, 'HH'); }
912 },
913 {
914 key: 'hh',
915 regex: '0[0-9]|1[0-2]',
916 apply: function(value) { this.hours = +value; },
917 formatter: function(date) { return dateFilter(date, 'hh'); }
918 },
919 {
920 key: 'H',
921 regex: '1?[0-9]|2[0-3]',
922 apply: function(value) { this.hours = +value; },
923 formatter: function(date) { return dateFilter(date, 'H'); }
924 },
925 {
926 key: 'h',
927 regex: '[0-9]|1[0-2]',
928 apply: function(value) { this.hours = +value; },
929 formatter: function(date) { return dateFilter(date, 'h'); }
930 },
931 {
932 key: 'mm',
933 regex: '[0-5][0-9]',
934 apply: function(value) { this.minutes = +value; },
935 formatter: function(date) { return dateFilter(date, 'mm'); }
936 },
937 {
938 key: 'm',
939 regex: '[0-9]|[1-5][0-9]',
940 apply: function(value) { this.minutes = +value; },
941 formatter: function(date) { return dateFilter(date, 'm'); }
942 },
943 {
944 key: 'sss',
945 regex: '[0-9][0-9][0-9]',
946 apply: function(value) { this.milliseconds = +value; },
947 formatter: function(date) { return dateFilter(date, 'sss'); }
948 },
949 {
950 key: 'ss',
951 regex: '[0-5][0-9]',
952 apply: function(value) { this.seconds = +value; },
953 formatter: function(date) { return dateFilter(date, 'ss'); }
954 },
955 {
956 key: 's',
957 regex: '[0-9]|[1-5][0-9]',
958 apply: function(value) { this.seconds = +value; },
959 formatter: function(date) { return dateFilter(date, 's'); }
960 },
961 {
962 key: 'a',
963 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
964 apply: function(value) {
965 if (this.hours === 12) {
966 this.hours = 0;
967 }
968
969 if (value === 'PM') {
970 this.hours += 12;
971 }
972 },
973 formatter: function(date) { return dateFilter(date, 'a'); }
974 },
975 {
976 key: 'Z',
977 regex: '[+-]\\d{4}',
978 apply: function(value) {
979 var matches = value.match(/([+-])(\d{2})(\d{2})/),
980 sign = matches[1],
981 hours = matches[2],
982 minutes = matches[3];
983 this.hours += toInt(sign + hours);
984 this.minutes += toInt(sign + minutes);
985 },
986 formatter: function(date) {
987 return dateFilter(date, 'Z');
988 }
989 },
990 {
991 key: 'ww',
992 regex: '[0-4][0-9]|5[0-3]',
993 formatter: function(date) { return dateFilter(date, 'ww'); }
994 },
995 {
996 key: 'w',
997 regex: '[0-9]|[1-4][0-9]|5[0-3]',
998 formatter: function(date) { return dateFilter(date, 'w'); }
999 },
1000 {
1001 key: 'GGGG',
1002 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
1003 formatter: function(date) { return dateFilter(date, 'GGGG'); }
1004 },
1005 {
1006 key: 'GGG',
1007 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1008 formatter: function(date) { return dateFilter(date, 'GGG'); }
1009 },
1010 {
1011 key: 'GG',
1012 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1013 formatter: function(date) { return dateFilter(date, 'GG'); }
1014 },
1015 {
1016 key: 'G',
1017 regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
1018 formatter: function(date) { return dateFilter(date, 'G'); }
1019 }
1020 ];
Ed Tanous4758d5b2017-06-06 15:28:13 -07001021
1022 if (angular.version.major >= 1 && angular.version.minor > 4) {
1023 formatCodeToRegex.push({
1024 key: 'LLLL',
1025 regex: $locale.DATETIME_FORMATS.STANDALONEMONTH.join('|'),
1026 apply: function(value) { this.month = $locale.DATETIME_FORMATS.STANDALONEMONTH.indexOf(value); },
1027 formatter: function(date) { return dateFilter(date, 'LLLL'); }
1028 });
1029 }
Ed Tanous904063f2017-03-02 16:48:24 -08001030 };
1031
1032 this.init();
1033
Ed Tanous4758d5b2017-06-06 15:28:13 -07001034 function getFormatCodeToRegex(key) {
1035 return filterFilter(formatCodeToRegex, {key: key}, true)[0];
1036 }
1037
1038 this.getParser = function (key) {
1039 var f = getFormatCodeToRegex(key);
1040 return f && f.apply || null;
1041 };
1042
1043 this.overrideParser = function (key, parser) {
1044 var f = getFormatCodeToRegex(key);
1045 if (f && angular.isFunction(parser)) {
1046 this.parsers = {};
1047 f.apply = parser;
1048 }
1049 }.bind(this);
1050
Ed Tanous904063f2017-03-02 16:48:24 -08001051 function createParser(format) {
1052 var map = [], regex = format.split('');
1053
1054 // check for literal values
1055 var quoteIndex = format.indexOf('\'');
1056 if (quoteIndex > -1) {
1057 var inLiteral = false;
1058 format = format.split('');
1059 for (var i = quoteIndex; i < format.length; i++) {
1060 if (inLiteral) {
1061 if (format[i] === '\'') {
1062 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
1063 format[i+1] = '$';
1064 regex[i+1] = '';
1065 } else { // end of literal
1066 regex[i] = '';
1067 inLiteral = false;
1068 }
1069 }
1070 format[i] = '$';
1071 } else {
1072 if (format[i] === '\'') { // start of literal
1073 format[i] = '$';
1074 regex[i] = '';
1075 inLiteral = true;
1076 }
1077 }
1078 }
1079
1080 format = format.join('');
1081 }
1082
1083 angular.forEach(formatCodeToRegex, function(data) {
1084 var index = format.indexOf(data.key);
1085
1086 if (index > -1) {
1087 format = format.split('');
1088
1089 regex[index] = '(' + data.regex + ')';
1090 format[index] = '$'; // Custom symbol to define consumed part of format
1091 for (var i = index + 1, n = index + data.key.length; i < n; i++) {
1092 regex[i] = '';
1093 format[i] = '$';
1094 }
1095 format = format.join('');
1096
1097 map.push({
1098 index: index,
1099 key: data.key,
1100 apply: data.apply,
1101 matcher: data.regex
1102 });
1103 }
1104 });
1105
1106 return {
1107 regex: new RegExp('^' + regex.join('') + '$'),
1108 map: orderByFilter(map, 'index')
1109 };
1110 }
1111
1112 function createFormatter(format) {
1113 var formatters = [];
1114 var i = 0;
1115 var formatter, literalIdx;
1116 while (i < format.length) {
1117 if (angular.isNumber(literalIdx)) {
1118 if (format.charAt(i) === '\'') {
1119 if (i + 1 >= format.length || format.charAt(i + 1) !== '\'') {
1120 formatters.push(constructLiteralFormatter(format, literalIdx, i));
1121 literalIdx = null;
1122 }
1123 } else if (i === format.length) {
1124 while (literalIdx < format.length) {
1125 formatter = constructFormatterFromIdx(format, literalIdx);
1126 formatters.push(formatter);
1127 literalIdx = formatter.endIdx;
1128 }
1129 }
1130
1131 i++;
1132 continue;
1133 }
1134
1135 if (format.charAt(i) === '\'') {
1136 literalIdx = i;
1137 i++;
1138 continue;
1139 }
1140
1141 formatter = constructFormatterFromIdx(format, i);
1142
1143 formatters.push(formatter.parser);
1144 i = formatter.endIdx;
1145 }
1146
1147 return formatters;
1148 }
1149
1150 function constructLiteralFormatter(format, literalIdx, endIdx) {
1151 return function() {
1152 return format.substr(literalIdx + 1, endIdx - literalIdx - 1);
1153 };
1154 }
1155
1156 function constructFormatterFromIdx(format, i) {
1157 var currentPosStr = format.substr(i);
1158 for (var j = 0; j < formatCodeToRegex.length; j++) {
1159 if (new RegExp('^' + formatCodeToRegex[j].key).test(currentPosStr)) {
1160 var data = formatCodeToRegex[j];
1161 return {
1162 endIdx: i + data.key.length,
1163 parser: data.formatter
1164 };
1165 }
1166 }
1167
1168 return {
1169 endIdx: i + 1,
1170 parser: function() {
1171 return currentPosStr.charAt(0);
1172 }
1173 };
1174 }
1175
1176 this.filter = function(date, format) {
1177 if (!angular.isDate(date) || isNaN(date) || !format) {
1178 return '';
1179 }
1180
1181 format = $locale.DATETIME_FORMATS[format] || format;
1182
1183 if ($locale.id !== localeId) {
1184 this.init();
1185 }
1186
1187 if (!this.formatters[format]) {
1188 this.formatters[format] = createFormatter(format);
1189 }
1190
1191 var formatters = this.formatters[format];
1192
1193 return formatters.reduce(function(str, formatter) {
1194 return str + formatter(date);
1195 }, '');
1196 };
1197
1198 this.parse = function(input, format, baseDate) {
1199 if (!angular.isString(input) || !format) {
1200 return input;
1201 }
1202
1203 format = $locale.DATETIME_FORMATS[format] || format;
1204 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
1205
1206 if ($locale.id !== localeId) {
1207 this.init();
1208 }
1209
1210 if (!this.parsers[format]) {
1211 this.parsers[format] = createParser(format, 'apply');
1212 }
1213
1214 var parser = this.parsers[format],
1215 regex = parser.regex,
1216 map = parser.map,
1217 results = input.match(regex),
1218 tzOffset = false;
1219 if (results && results.length) {
1220 var fields, dt;
1221 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
1222 fields = {
1223 year: baseDate.getFullYear(),
1224 month: baseDate.getMonth(),
1225 date: baseDate.getDate(),
1226 hours: baseDate.getHours(),
1227 minutes: baseDate.getMinutes(),
1228 seconds: baseDate.getSeconds(),
1229 milliseconds: baseDate.getMilliseconds()
1230 };
1231 } else {
1232 if (baseDate) {
1233 $log.warn('dateparser:', 'baseDate is not a valid date');
1234 }
1235 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
1236 }
1237
1238 for (var i = 1, n = results.length; i < n; i++) {
1239 var mapper = map[i - 1];
1240 if (mapper.matcher === 'Z') {
1241 tzOffset = true;
1242 }
1243
1244 if (mapper.apply) {
1245 mapper.apply.call(fields, results[i]);
1246 }
1247 }
1248
1249 var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
1250 Date.prototype.setFullYear;
1251 var timesetter = tzOffset ? Date.prototype.setUTCHours :
1252 Date.prototype.setHours;
1253
1254 if (isValid(fields.year, fields.month, fields.date)) {
1255 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
1256 dt = new Date(baseDate);
1257 datesetter.call(dt, fields.year, fields.month, fields.date);
1258 timesetter.call(dt, fields.hours, fields.minutes,
1259 fields.seconds, fields.milliseconds);
1260 } else {
1261 dt = new Date(0);
1262 datesetter.call(dt, fields.year, fields.month, fields.date);
1263 timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
1264 fields.seconds || 0, fields.milliseconds || 0);
1265 }
1266 }
1267
1268 return dt;
1269 }
1270 };
1271
1272 // Check if date is valid for specific month (and year for February).
1273 // Month: 0 = Jan, 1 = Feb, etc
1274 function isValid(year, month, date) {
1275 if (date < 1) {
1276 return false;
1277 }
1278
1279 if (month === 1 && date > 28) {
1280 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
1281 }
1282
1283 if (month === 3 || month === 5 || month === 8 || month === 10) {
1284 return date < 31;
1285 }
1286
1287 return true;
1288 }
1289
1290 function toInt(str) {
1291 return parseInt(str, 10);
1292 }
1293
1294 this.toTimezone = toTimezone;
1295 this.fromTimezone = fromTimezone;
1296 this.timezoneToOffset = timezoneToOffset;
1297 this.addDateMinutes = addDateMinutes;
1298 this.convertTimezoneToLocal = convertTimezoneToLocal;
1299
1300 function toTimezone(date, timezone) {
1301 return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
1302 }
1303
1304 function fromTimezone(date, timezone) {
1305 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
1306 }
1307
1308 //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207
1309 function timezoneToOffset(timezone, fallback) {
1310 timezone = timezone.replace(/:/g, '');
1311 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1312 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1313 }
1314
1315 function addDateMinutes(date, minutes) {
1316 date = new Date(date.getTime());
1317 date.setMinutes(date.getMinutes() + minutes);
1318 return date;
1319 }
1320
1321 function convertTimezoneToLocal(date, timezone, reverse) {
1322 reverse = reverse ? -1 : 1;
1323 var dateTimezoneOffset = date.getTimezoneOffset();
1324 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);
1325 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));
1326 }
1327}]);
1328
1329// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
1330// at most one element.
1331angular.module('ui.bootstrap.isClass', [])
1332.directive('uibIsClass', [
1333 '$animate',
1334function ($animate) {
1335 // 11111111 22222222
1336 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
1337 // 11111111 22222222
1338 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
1339
1340 var dataPerTracked = {};
1341
1342 return {
1343 restrict: 'A',
1344 compile: function(tElement, tAttrs) {
1345 var linkedScopes = [];
1346 var instances = [];
1347 var expToData = {};
1348 var lastActivated = null;
1349 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
1350 var onExp = onExpMatches[2];
1351 var expsStr = onExpMatches[1];
1352 var exps = expsStr.split(',');
1353
1354 return linkFn;
1355
1356 function linkFn(scope, element, attrs) {
1357 linkedScopes.push(scope);
1358 instances.push({
1359 scope: scope,
1360 element: element
1361 });
1362
1363 exps.forEach(function(exp, k) {
1364 addForExp(exp, scope);
1365 });
1366
1367 scope.$on('$destroy', removeScope);
1368 }
1369
1370 function addForExp(exp, scope) {
1371 var matches = exp.match(IS_REGEXP);
1372 var clazz = scope.$eval(matches[1]);
1373 var compareWithExp = matches[2];
1374 var data = expToData[exp];
1375 if (!data) {
1376 var watchFn = function(compareWithVal) {
1377 var newActivated = null;
1378 instances.some(function(instance) {
1379 var thisVal = instance.scope.$eval(onExp);
1380 if (thisVal === compareWithVal) {
1381 newActivated = instance;
1382 return true;
1383 }
1384 });
1385 if (data.lastActivated !== newActivated) {
1386 if (data.lastActivated) {
1387 $animate.removeClass(data.lastActivated.element, clazz);
1388 }
1389 if (newActivated) {
1390 $animate.addClass(newActivated.element, clazz);
1391 }
1392 data.lastActivated = newActivated;
1393 }
1394 };
1395 expToData[exp] = data = {
1396 lastActivated: null,
1397 scope: scope,
1398 watchFn: watchFn,
1399 compareWithExp: compareWithExp,
1400 watcher: scope.$watch(compareWithExp, watchFn)
1401 };
1402 }
1403 data.watchFn(scope.$eval(compareWithExp));
1404 }
1405
1406 function removeScope(e) {
1407 var removedScope = e.targetScope;
1408 var index = linkedScopes.indexOf(removedScope);
1409 linkedScopes.splice(index, 1);
1410 instances.splice(index, 1);
1411 if (linkedScopes.length) {
1412 var newWatchScope = linkedScopes[0];
1413 angular.forEach(expToData, function(data) {
1414 if (data.scope === removedScope) {
1415 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
1416 data.scope = newWatchScope;
1417 }
1418 });
1419 } else {
1420 expToData = {};
1421 }
1422 }
1423 }
1424 };
1425}]);
1426angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
1427
1428.value('$datepickerSuppressError', false)
1429
1430.value('$datepickerLiteralWarning', true)
1431
1432.constant('uibDatepickerConfig', {
1433 datepickerMode: 'day',
1434 formatDay: 'dd',
1435 formatMonth: 'MMMM',
1436 formatYear: 'yyyy',
1437 formatDayHeader: 'EEE',
1438 formatDayTitle: 'MMMM yyyy',
1439 formatMonthTitle: 'yyyy',
1440 maxDate: null,
1441 maxMode: 'year',
1442 minDate: null,
1443 minMode: 'day',
1444 monthColumns: 3,
1445 ngModelOptions: {},
1446 shortcutPropagation: false,
1447 showWeeks: true,
1448 yearColumns: 5,
1449 yearRows: 4
1450})
1451
1452.controller('UibDatepickerController', ['$scope', '$element', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
1453 function($scope, $element, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
1454 var self = this,
1455 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
1456 ngModelOptions = {},
1457 watchListeners = [];
1458
1459 $element.addClass('uib-datepicker');
1460 $attrs.$set('role', 'application');
1461
1462 if (!$scope.datepickerOptions) {
1463 $scope.datepickerOptions = {};
1464 }
1465
1466 // Modes chain
1467 this.modes = ['day', 'month', 'year'];
1468
1469 [
1470 'customClass',
1471 'dateDisabled',
1472 'datepickerMode',
1473 'formatDay',
1474 'formatDayHeader',
1475 'formatDayTitle',
1476 'formatMonth',
1477 'formatMonthTitle',
1478 'formatYear',
1479 'maxDate',
1480 'maxMode',
1481 'minDate',
1482 'minMode',
1483 'monthColumns',
1484 'showWeeks',
1485 'shortcutPropagation',
1486 'startingDay',
1487 'yearColumns',
1488 'yearRows'
1489 ].forEach(function(key) {
1490 switch (key) {
1491 case 'customClass':
1492 case 'dateDisabled':
1493 $scope[key] = $scope.datepickerOptions[key] || angular.noop;
1494 break;
1495 case 'datepickerMode':
1496 $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
1497 $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
1498 break;
1499 case 'formatDay':
1500 case 'formatDayHeader':
1501 case 'formatDayTitle':
1502 case 'formatMonth':
1503 case 'formatMonthTitle':
1504 case 'formatYear':
1505 self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1506 $interpolate($scope.datepickerOptions[key])($scope.$parent) :
1507 datepickerConfig[key];
1508 break;
1509 case 'monthColumns':
1510 case 'showWeeks':
1511 case 'shortcutPropagation':
1512 case 'yearColumns':
1513 case 'yearRows':
1514 self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1515 $scope.datepickerOptions[key] : datepickerConfig[key];
1516 break;
1517 case 'startingDay':
1518 if (angular.isDefined($scope.datepickerOptions.startingDay)) {
1519 self.startingDay = $scope.datepickerOptions.startingDay;
1520 } else if (angular.isNumber(datepickerConfig.startingDay)) {
1521 self.startingDay = datepickerConfig.startingDay;
1522 } else {
1523 self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
1524 }
1525
1526 break;
1527 case 'maxDate':
1528 case 'minDate':
1529 $scope.$watch('datepickerOptions.' + key, function(value) {
1530 if (value) {
1531 if (angular.isDate(value)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001532 self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001533 } else {
1534 if ($datepickerLiteralWarning) {
1535 $log.warn('Literal date support has been deprecated, please switch to date object usage');
1536 }
1537
1538 self[key] = new Date(dateFilter(value, 'medium'));
1539 }
1540 } else {
1541 self[key] = datepickerConfig[key] ?
Ed Tanous4758d5b2017-06-06 15:28:13 -07001542 dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.getOption('timezone')) :
Ed Tanous904063f2017-03-02 16:48:24 -08001543 null;
1544 }
1545
1546 self.refreshView();
1547 });
1548
1549 break;
1550 case 'maxMode':
1551 case 'minMode':
1552 if ($scope.datepickerOptions[key]) {
1553 $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
1554 self[key] = $scope[key] = angular.isDefined(value) ? value : $scope.datepickerOptions[key];
1555 if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
1556 key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
1557 $scope.datepickerMode = self[key];
1558 $scope.datepickerOptions.datepickerMode = self[key];
1559 }
1560 });
1561 } else {
1562 self[key] = $scope[key] = datepickerConfig[key] || null;
1563 }
1564
1565 break;
1566 }
1567 });
1568
1569 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1570
1571 $scope.disabled = angular.isDefined($attrs.disabled) || false;
1572 if (angular.isDefined($attrs.ngDisabled)) {
1573 watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
1574 $scope.disabled = disabled;
1575 self.refreshView();
1576 }));
1577 }
1578
1579 $scope.isActive = function(dateObject) {
1580 if (self.compare(dateObject.date, self.activeDate) === 0) {
1581 $scope.activeDateId = dateObject.uid;
1582 return true;
1583 }
1584 return false;
1585 };
1586
1587 this.init = function(ngModelCtrl_) {
1588 ngModelCtrl = ngModelCtrl_;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001589 ngModelOptions = extractOptions(ngModelCtrl);
1590
Ed Tanous904063f2017-03-02 16:48:24 -08001591 if ($scope.datepickerOptions.initDate) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001592 self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.getOption('timezone')) || new Date();
Ed Tanous904063f2017-03-02 16:48:24 -08001593 $scope.$watch('datepickerOptions.initDate', function(initDate) {
1594 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001595 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001596 self.refreshView();
1597 }
1598 });
1599 } else {
1600 self.activeDate = new Date();
1601 }
1602
1603 var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date();
1604 this.activeDate = !isNaN(date) ?
Ed Tanous4758d5b2017-06-06 15:28:13 -07001605 dateParser.fromTimezone(date, ngModelOptions.getOption('timezone')) :
1606 dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001607
1608 ngModelCtrl.$render = function() {
1609 self.render();
1610 };
1611 };
1612
1613 this.render = function() {
1614 if (ngModelCtrl.$viewValue) {
1615 var date = new Date(ngModelCtrl.$viewValue),
1616 isValid = !isNaN(date);
1617
1618 if (isValid) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001619 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001620 } else if (!$datepickerSuppressError) {
1621 $log.error('Datepicker directive: "ng-model" value must be a Date object');
1622 }
1623 }
1624 this.refreshView();
1625 };
1626
1627 this.refreshView = function() {
1628 if (this.element) {
1629 $scope.selectedDt = null;
1630 this._refreshView();
1631 if ($scope.activeDt) {
1632 $scope.activeDateId = $scope.activeDt.uid;
1633 }
1634
1635 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001636 date = dateParser.fromTimezone(date, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001637 ngModelCtrl.$setValidity('dateDisabled', !date ||
1638 this.element && !this.isDisabled(date));
1639 }
1640 };
1641
1642 this.createDateObject = function(date, format) {
1643 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07001644 model = dateParser.fromTimezone(model, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001645 var today = new Date();
Ed Tanous4758d5b2017-06-06 15:28:13 -07001646 today = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001647 var time = this.compare(date, today);
1648 var dt = {
1649 date: date,
1650 label: dateParser.filter(date, format),
1651 selected: model && this.compare(date, model) === 0,
1652 disabled: this.isDisabled(date),
1653 past: time < 0,
1654 current: time === 0,
1655 future: time > 0,
1656 customClass: this.customClass(date) || null
1657 };
1658
1659 if (model && this.compare(date, model) === 0) {
1660 $scope.selectedDt = dt;
1661 }
1662
1663 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
1664 $scope.activeDt = dt;
1665 }
1666
1667 return dt;
1668 };
1669
1670 this.isDisabled = function(date) {
1671 return $scope.disabled ||
1672 this.minDate && this.compare(date, this.minDate) < 0 ||
1673 this.maxDate && this.compare(date, this.maxDate) > 0 ||
1674 $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
1675 };
1676
1677 this.customClass = function(date) {
1678 return $scope.customClass({date: date, mode: $scope.datepickerMode});
1679 };
1680
1681 // Split array into smaller arrays
1682 this.split = function(arr, size) {
1683 var arrays = [];
1684 while (arr.length > 0) {
1685 arrays.push(arr.splice(0, size));
1686 }
1687 return arrays;
1688 };
1689
1690 $scope.select = function(date) {
1691 if ($scope.datepickerMode === self.minMode) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07001692 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.getOption('timezone')) : new Date(0, 0, 0, 0, 0, 0, 0);
Ed Tanous904063f2017-03-02 16:48:24 -08001693 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
Ed Tanous4758d5b2017-06-06 15:28:13 -07001694 dt = dateParser.toTimezone(dt, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08001695 ngModelCtrl.$setViewValue(dt);
1696 ngModelCtrl.$render();
1697 } else {
1698 self.activeDate = date;
1699 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
1700
1701 $scope.$emit('uib:datepicker.mode');
1702 }
1703
1704 $scope.$broadcast('uib:datepicker.focus');
1705 };
1706
1707 $scope.move = function(direction) {
1708 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1709 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1710 self.activeDate.setFullYear(year, month, 1);
1711 self.refreshView();
1712 };
1713
1714 $scope.toggleMode = function(direction) {
1715 direction = direction || 1;
1716
1717 if ($scope.datepickerMode === self.maxMode && direction === 1 ||
1718 $scope.datepickerMode === self.minMode && direction === -1) {
1719 return;
1720 }
1721
1722 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
1723
1724 $scope.$emit('uib:datepicker.mode');
1725 };
1726
1727 // Key event mapper
1728 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
1729
1730 var focusElement = function() {
1731 self.element[0].focus();
1732 };
1733
1734 // Listen for focus requests from popup directive
1735 $scope.$on('uib:datepicker.focus', focusElement);
1736
1737 $scope.keydown = function(evt) {
1738 var key = $scope.keys[evt.which];
1739
1740 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
1741 return;
1742 }
1743
1744 evt.preventDefault();
1745 if (!self.shortcutPropagation) {
1746 evt.stopPropagation();
1747 }
1748
1749 if (key === 'enter' || key === 'space') {
1750 if (self.isDisabled(self.activeDate)) {
1751 return; // do nothing
1752 }
1753 $scope.select(self.activeDate);
1754 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
1755 $scope.toggleMode(key === 'up' ? 1 : -1);
1756 } else {
1757 self.handleKeyDown(key, evt);
1758 self.refreshView();
1759 }
1760 };
1761
1762 $element.on('keydown', function(evt) {
1763 $scope.$apply(function() {
1764 $scope.keydown(evt);
1765 });
1766 });
1767
1768 $scope.$on('$destroy', function() {
1769 //Clear all watch listeners on destroy
1770 while (watchListeners.length) {
1771 watchListeners.shift()();
1772 }
1773 });
1774
1775 function setMode(mode) {
1776 $scope.datepickerMode = mode;
1777 $scope.datepickerOptions.datepickerMode = mode;
1778 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07001779
1780 function extractOptions(ngModelCtrl) {
1781 var ngModelOptions;
1782
1783 if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
1784 // guarantee a value
1785 ngModelOptions = ngModelCtrl.$options ||
1786 $scope.datepickerOptions.ngModelOptions ||
1787 datepickerConfig.ngModelOptions ||
1788 {};
1789
1790 // mimic 1.6+ api
1791 ngModelOptions.getOption = function (key) {
1792 return ngModelOptions[key];
1793 };
1794 } else { // in angular >=1.6 $options is always present
1795 // ng-model-options defaults timezone to null; don't let its precedence squash a non-null value
1796 var timezone = ngModelCtrl.$options.getOption('timezone') ||
1797 ($scope.datepickerOptions.ngModelOptions ? $scope.datepickerOptions.ngModelOptions.timezone : null) ||
1798 (datepickerConfig.ngModelOptions ? datepickerConfig.ngModelOptions.timezone : null);
1799
1800 // values passed to createChild override existing values
1801 ngModelOptions = ngModelCtrl.$options // start with a ModelOptions instance
1802 .createChild(datepickerConfig.ngModelOptions) // lowest precedence
1803 .createChild($scope.datepickerOptions.ngModelOptions)
1804 .createChild(ngModelCtrl.$options) // highest precedence
1805 .createChild({timezone: timezone}); // to keep from squashing a non-null value
1806 }
1807
1808 return ngModelOptions;
1809 }
Ed Tanous904063f2017-03-02 16:48:24 -08001810}])
1811
1812.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1813 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1814
1815 this.step = { months: 1 };
1816 this.element = $element;
1817 function getDaysInMonth(year, month) {
1818 return month === 1 && year % 4 === 0 &&
1819 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
1820 }
1821
1822 this.init = function(ctrl) {
1823 angular.extend(ctrl, this);
1824 scope.showWeeks = ctrl.showWeeks;
1825 ctrl.refreshView();
1826 };
1827
1828 this.getDates = function(startDate, n) {
1829 var dates = new Array(n), current = new Date(startDate), i = 0, date;
1830 while (i < n) {
1831 date = new Date(current);
1832 dates[i++] = date;
1833 current.setDate(current.getDate() + 1);
1834 }
1835 return dates;
1836 };
1837
1838 this._refreshView = function() {
1839 var year = this.activeDate.getFullYear(),
1840 month = this.activeDate.getMonth(),
1841 firstDayOfMonth = new Date(this.activeDate);
1842
1843 firstDayOfMonth.setFullYear(year, month, 1);
1844
1845 var difference = this.startingDay - firstDayOfMonth.getDay(),
1846 numDisplayedFromPreviousMonth = difference > 0 ?
1847 7 - difference : - difference,
1848 firstDate = new Date(firstDayOfMonth);
1849
1850 if (numDisplayedFromPreviousMonth > 0) {
1851 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1852 }
1853
1854 // 42 is the number of days on a six-week calendar
1855 var days = this.getDates(firstDate, 42);
1856 for (var i = 0; i < 42; i ++) {
1857 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
1858 secondary: days[i].getMonth() !== month,
1859 uid: scope.uniqueId + '-' + i
1860 });
1861 }
1862
1863 scope.labels = new Array(7);
1864 for (var j = 0; j < 7; j++) {
1865 scope.labels[j] = {
1866 abbr: dateFilter(days[j].date, this.formatDayHeader),
1867 full: dateFilter(days[j].date, 'EEEE')
1868 };
1869 }
1870
1871 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
1872 scope.rows = this.split(days, 7);
1873
1874 if (scope.showWeeks) {
1875 scope.weekNumbers = [];
1876 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
1877 numWeeks = scope.rows.length;
1878 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1879 scope.weekNumbers.push(
1880 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
1881 }
1882 }
1883 };
1884
1885 this.compare = function(date1, date2) {
1886 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
1887 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
1888 _date1.setFullYear(date1.getFullYear());
1889 _date2.setFullYear(date2.getFullYear());
1890 return _date1 - _date2;
1891 };
1892
1893 function getISO8601WeekNumber(date) {
1894 var checkDate = new Date(date);
1895 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1896 var time = checkDate.getTime();
1897 checkDate.setMonth(0); // Compare with Jan 1
1898 checkDate.setDate(1);
1899 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1900 }
1901
1902 this.handleKeyDown = function(key, evt) {
1903 var date = this.activeDate.getDate();
1904
1905 if (key === 'left') {
1906 date = date - 1;
1907 } else if (key === 'up') {
1908 date = date - 7;
1909 } else if (key === 'right') {
1910 date = date + 1;
1911 } else if (key === 'down') {
1912 date = date + 7;
1913 } else if (key === 'pageup' || key === 'pagedown') {
1914 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
1915 this.activeDate.setMonth(month, 1);
1916 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
1917 } else if (key === 'home') {
1918 date = 1;
1919 } else if (key === 'end') {
1920 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
1921 }
1922 this.activeDate.setDate(date);
1923 };
1924}])
1925
1926.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1927 this.step = { years: 1 };
1928 this.element = $element;
1929
1930 this.init = function(ctrl) {
1931 angular.extend(ctrl, this);
1932 ctrl.refreshView();
1933 };
1934
1935 this._refreshView = function() {
1936 var months = new Array(12),
1937 year = this.activeDate.getFullYear(),
1938 date;
1939
1940 for (var i = 0; i < 12; i++) {
1941 date = new Date(this.activeDate);
1942 date.setFullYear(year, i, 1);
1943 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
1944 uid: scope.uniqueId + '-' + i
1945 });
1946 }
1947
1948 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
1949 scope.rows = this.split(months, this.monthColumns);
1950 scope.yearHeaderColspan = this.monthColumns > 3 ? this.monthColumns - 2 : 1;
1951 };
1952
1953 this.compare = function(date1, date2) {
1954 var _date1 = new Date(date1.getFullYear(), date1.getMonth());
1955 var _date2 = new Date(date2.getFullYear(), date2.getMonth());
1956 _date1.setFullYear(date1.getFullYear());
1957 _date2.setFullYear(date2.getFullYear());
1958 return _date1 - _date2;
1959 };
1960
1961 this.handleKeyDown = function(key, evt) {
1962 var date = this.activeDate.getMonth();
1963
1964 if (key === 'left') {
1965 date = date - 1;
1966 } else if (key === 'up') {
1967 date = date - this.monthColumns;
1968 } else if (key === 'right') {
1969 date = date + 1;
1970 } else if (key === 'down') {
1971 date = date + this.monthColumns;
1972 } else if (key === 'pageup' || key === 'pagedown') {
1973 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
1974 this.activeDate.setFullYear(year);
1975 } else if (key === 'home') {
1976 date = 0;
1977 } else if (key === 'end') {
1978 date = 11;
1979 }
1980 this.activeDate.setMonth(date);
1981 };
1982}])
1983
1984.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1985 var columns, range;
1986 this.element = $element;
1987
1988 function getStartingYear(year) {
1989 return parseInt((year - 1) / range, 10) * range + 1;
1990 }
1991
1992 this.yearpickerInit = function() {
1993 columns = this.yearColumns;
1994 range = this.yearRows * columns;
1995 this.step = { years: range };
1996 };
1997
1998 this._refreshView = function() {
1999 var years = new Array(range), date;
2000
2001 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
2002 date = new Date(this.activeDate);
2003 date.setFullYear(start + i, 0, 1);
2004 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
2005 uid: scope.uniqueId + '-' + i
2006 });
2007 }
2008
2009 scope.title = [years[0].label, years[range - 1].label].join(' - ');
2010 scope.rows = this.split(years, columns);
2011 scope.columns = columns;
2012 };
2013
2014 this.compare = function(date1, date2) {
2015 return date1.getFullYear() - date2.getFullYear();
2016 };
2017
2018 this.handleKeyDown = function(key, evt) {
2019 var date = this.activeDate.getFullYear();
2020
2021 if (key === 'left') {
2022 date = date - 1;
2023 } else if (key === 'up') {
2024 date = date - columns;
2025 } else if (key === 'right') {
2026 date = date + 1;
2027 } else if (key === 'down') {
2028 date = date + columns;
2029 } else if (key === 'pageup' || key === 'pagedown') {
2030 date += (key === 'pageup' ? - 1 : 1) * range;
2031 } else if (key === 'home') {
2032 date = getStartingYear(this.activeDate.getFullYear());
2033 } else if (key === 'end') {
2034 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
2035 }
2036 this.activeDate.setFullYear(date);
2037 };
2038}])
2039
2040.directive('uibDatepicker', function() {
2041 return {
2042 templateUrl: function(element, attrs) {
2043 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
2044 },
2045 scope: {
2046 datepickerOptions: '=?'
2047 },
2048 require: ['uibDatepicker', '^ngModel'],
2049 restrict: 'A',
2050 controller: 'UibDatepickerController',
2051 controllerAs: 'datepicker',
2052 link: function(scope, element, attrs, ctrls) {
2053 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2054
2055 datepickerCtrl.init(ngModelCtrl);
2056 }
2057 };
2058})
2059
2060.directive('uibDaypicker', function() {
2061 return {
2062 templateUrl: function(element, attrs) {
2063 return attrs.templateUrl || 'uib/template/datepicker/day.html';
2064 },
2065 require: ['^uibDatepicker', 'uibDaypicker'],
2066 restrict: 'A',
2067 controller: 'UibDaypickerController',
2068 link: function(scope, element, attrs, ctrls) {
2069 var datepickerCtrl = ctrls[0],
2070 daypickerCtrl = ctrls[1];
2071
2072 daypickerCtrl.init(datepickerCtrl);
2073 }
2074 };
2075})
2076
2077.directive('uibMonthpicker', function() {
2078 return {
2079 templateUrl: function(element, attrs) {
2080 return attrs.templateUrl || 'uib/template/datepicker/month.html';
2081 },
2082 require: ['^uibDatepicker', 'uibMonthpicker'],
2083 restrict: 'A',
2084 controller: 'UibMonthpickerController',
2085 link: function(scope, element, attrs, ctrls) {
2086 var datepickerCtrl = ctrls[0],
2087 monthpickerCtrl = ctrls[1];
2088
2089 monthpickerCtrl.init(datepickerCtrl);
2090 }
2091 };
2092})
2093
2094.directive('uibYearpicker', function() {
2095 return {
2096 templateUrl: function(element, attrs) {
2097 return attrs.templateUrl || 'uib/template/datepicker/year.html';
2098 },
2099 require: ['^uibDatepicker', 'uibYearpicker'],
2100 restrict: 'A',
2101 controller: 'UibYearpickerController',
2102 link: function(scope, element, attrs, ctrls) {
2103 var ctrl = ctrls[0];
2104 angular.extend(ctrl, ctrls[1]);
2105 ctrl.yearpickerInit();
2106
2107 ctrl.refreshView();
2108 }
2109 };
2110});
2111
2112angular.module('ui.bootstrap.position', [])
2113
2114/**
2115 * A set of utility methods for working with the DOM.
2116 * It is meant to be used where we need to absolute-position elements in
2117 * relation to another element (this is the case for tooltips, popovers,
2118 * typeahead suggestions etc.).
2119 */
2120 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
2121 /**
2122 * Used by scrollbarWidth() function to cache scrollbar's width.
2123 * Do not access this variable directly, use scrollbarWidth() instead.
2124 */
2125 var SCROLLBAR_WIDTH;
2126 /**
2127 * scrollbar on body and html element in IE and Edge overlay
2128 * content and should be considered 0 width.
2129 */
2130 var BODY_SCROLLBAR_WIDTH;
2131 var OVERFLOW_REGEX = {
2132 normal: /(auto|scroll)/,
2133 hidden: /(auto|scroll|hidden)/
2134 };
2135 var PLACEMENT_REGEX = {
2136 auto: /\s?auto?\s?/i,
2137 primary: /^(top|bottom|left|right)$/,
2138 secondary: /^(top|bottom|left|right|center)$/,
2139 vertical: /^(top|bottom)$/
2140 };
2141 var BODY_REGEX = /(HTML|BODY)/;
2142
2143 return {
2144
2145 /**
2146 * Provides a raw DOM element from a jQuery/jQLite element.
2147 *
2148 * @param {element} elem - The element to convert.
2149 *
2150 * @returns {element} A HTML element.
2151 */
2152 getRawNode: function(elem) {
2153 return elem.nodeName ? elem : elem[0] || elem;
2154 },
2155
2156 /**
2157 * Provides a parsed number for a style property. Strips
2158 * units and casts invalid numbers to 0.
2159 *
2160 * @param {string} value - The style value to parse.
2161 *
2162 * @returns {number} A valid number.
2163 */
2164 parseStyle: function(value) {
2165 value = parseFloat(value);
2166 return isFinite(value) ? value : 0;
2167 },
2168
2169 /**
2170 * Provides the closest positioned ancestor.
2171 *
2172 * @param {element} element - The element to get the offest parent for.
2173 *
2174 * @returns {element} The closest positioned ancestor.
2175 */
2176 offsetParent: function(elem) {
2177 elem = this.getRawNode(elem);
2178
2179 var offsetParent = elem.offsetParent || $document[0].documentElement;
2180
2181 function isStaticPositioned(el) {
2182 return ($window.getComputedStyle(el).position || 'static') === 'static';
2183 }
2184
2185 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
2186 offsetParent = offsetParent.offsetParent;
2187 }
2188
2189 return offsetParent || $document[0].documentElement;
2190 },
2191
2192 /**
2193 * Provides the scrollbar width, concept from TWBS measureScrollbar()
2194 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
2195 * In IE and Edge, scollbar on body and html element overlay and should
2196 * return a width of 0.
2197 *
2198 * @returns {number} The width of the browser scollbar.
2199 */
2200 scrollbarWidth: function(isBody) {
2201 if (isBody) {
2202 if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
2203 var bodyElem = $document.find('body');
2204 bodyElem.addClass('uib-position-body-scrollbar-measure');
2205 BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
2206 BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
2207 bodyElem.removeClass('uib-position-body-scrollbar-measure');
2208 }
2209 return BODY_SCROLLBAR_WIDTH;
2210 }
2211
2212 if (angular.isUndefined(SCROLLBAR_WIDTH)) {
2213 var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
2214 $document.find('body').append(scrollElem);
2215 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
2216 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
2217 scrollElem.remove();
2218 }
2219
2220 return SCROLLBAR_WIDTH;
2221 },
2222
2223 /**
2224 * Provides the padding required on an element to replace the scrollbar.
2225 *
2226 * @returns {object} An object with the following properties:
2227 * <ul>
2228 * <li>**scrollbarWidth**: the width of the scrollbar</li>
2229 * <li>**widthOverflow**: whether the the width is overflowing</li>
2230 * <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
2231 * <li>**rightOriginal**: the amount of right padding currently on the element</li>
2232 * <li>**heightOverflow**: whether the the height is overflowing</li>
2233 * <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
2234 * <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
2235 * </ul>
2236 */
2237 scrollbarPadding: function(elem) {
2238 elem = this.getRawNode(elem);
2239
2240 var elemStyle = $window.getComputedStyle(elem);
2241 var paddingRight = this.parseStyle(elemStyle.paddingRight);
2242 var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
2243 var scrollParent = this.scrollParent(elem, false, true);
Ed Tanous4758d5b2017-06-06 15:28:13 -07002244 var scrollbarWidth = this.scrollbarWidth(BODY_REGEX.test(scrollParent.tagName));
Ed Tanous904063f2017-03-02 16:48:24 -08002245
2246 return {
2247 scrollbarWidth: scrollbarWidth,
2248 widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
2249 right: paddingRight + scrollbarWidth,
2250 originalRight: paddingRight,
2251 heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
2252 bottom: paddingBottom + scrollbarWidth,
2253 originalBottom: paddingBottom
2254 };
2255 },
2256
2257 /**
2258 * Checks to see if the element is scrollable.
2259 *
2260 * @param {element} elem - The element to check.
2261 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2262 * default is false.
2263 *
2264 * @returns {boolean} Whether the element is scrollable.
2265 */
2266 isScrollable: function(elem, includeHidden) {
2267 elem = this.getRawNode(elem);
2268
2269 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2270 var elemStyle = $window.getComputedStyle(elem);
2271 return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
2272 },
2273
2274 /**
2275 * Provides the closest scrollable ancestor.
2276 * A port of the jQuery UI scrollParent method:
2277 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
2278 *
2279 * @param {element} elem - The element to find the scroll parent of.
2280 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2281 * default is false.
2282 * @param {boolean=} [includeSelf=false] - Should the element being passed be
2283 * included in the scrollable llokup.
2284 *
2285 * @returns {element} A HTML element.
2286 */
2287 scrollParent: function(elem, includeHidden, includeSelf) {
2288 elem = this.getRawNode(elem);
2289
2290 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2291 var documentEl = $document[0].documentElement;
2292 var elemStyle = $window.getComputedStyle(elem);
2293 if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
2294 return elem;
2295 }
2296 var excludeStatic = elemStyle.position === 'absolute';
2297 var scrollParent = elem.parentElement || documentEl;
2298
2299 if (scrollParent === documentEl || elemStyle.position === 'fixed') {
2300 return documentEl;
2301 }
2302
2303 while (scrollParent.parentElement && scrollParent !== documentEl) {
2304 var spStyle = $window.getComputedStyle(scrollParent);
2305 if (excludeStatic && spStyle.position !== 'static') {
2306 excludeStatic = false;
2307 }
2308
2309 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
2310 break;
2311 }
2312 scrollParent = scrollParent.parentElement;
2313 }
2314
2315 return scrollParent;
2316 },
2317
2318 /**
2319 * Provides read-only equivalent of jQuery's position function:
2320 * http://api.jquery.com/position/ - distance to closest positioned
2321 * ancestor. Does not account for margins by default like jQuery position.
2322 *
2323 * @param {element} elem - The element to caclulate the position on.
2324 * @param {boolean=} [includeMargins=false] - Should margins be accounted
2325 * for, default is false.
2326 *
2327 * @returns {object} An object with the following properties:
2328 * <ul>
2329 * <li>**width**: the width of the element</li>
2330 * <li>**height**: the height of the element</li>
2331 * <li>**top**: distance to top edge of offset parent</li>
2332 * <li>**left**: distance to left edge of offset parent</li>
2333 * </ul>
2334 */
2335 position: function(elem, includeMagins) {
2336 elem = this.getRawNode(elem);
2337
2338 var elemOffset = this.offset(elem);
2339 if (includeMagins) {
2340 var elemStyle = $window.getComputedStyle(elem);
2341 elemOffset.top -= this.parseStyle(elemStyle.marginTop);
2342 elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
2343 }
2344 var parent = this.offsetParent(elem);
2345 var parentOffset = {top: 0, left: 0};
2346
2347 if (parent !== $document[0].documentElement) {
2348 parentOffset = this.offset(parent);
2349 parentOffset.top += parent.clientTop - parent.scrollTop;
2350 parentOffset.left += parent.clientLeft - parent.scrollLeft;
2351 }
2352
2353 return {
2354 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
2355 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
2356 top: Math.round(elemOffset.top - parentOffset.top),
2357 left: Math.round(elemOffset.left - parentOffset.left)
2358 };
2359 },
2360
2361 /**
2362 * Provides read-only equivalent of jQuery's offset function:
2363 * http://api.jquery.com/offset/ - distance to viewport. Does
2364 * not account for borders, margins, or padding on the body
2365 * element.
2366 *
2367 * @param {element} elem - The element to calculate the offset on.
2368 *
2369 * @returns {object} An object with the following properties:
2370 * <ul>
2371 * <li>**width**: the width of the element</li>
2372 * <li>**height**: the height of the element</li>
2373 * <li>**top**: distance to top edge of viewport</li>
2374 * <li>**right**: distance to bottom edge of viewport</li>
2375 * </ul>
2376 */
2377 offset: function(elem) {
2378 elem = this.getRawNode(elem);
2379
2380 var elemBCR = elem.getBoundingClientRect();
2381 return {
2382 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
2383 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
2384 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
2385 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
2386 };
2387 },
2388
2389 /**
2390 * Provides offset distance to the closest scrollable ancestor
2391 * or viewport. Accounts for border and scrollbar width.
2392 *
2393 * Right and bottom dimensions represent the distance to the
2394 * respective edge of the viewport element. If the element
2395 * edge extends beyond the viewport, a negative value will be
2396 * reported.
2397 *
2398 * @param {element} elem - The element to get the viewport offset for.
2399 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
2400 * of the first scrollable element, default is false.
2401 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
2402 * be accounted for, default is true.
2403 *
2404 * @returns {object} An object with the following properties:
2405 * <ul>
2406 * <li>**top**: distance to the top content edge of viewport element</li>
2407 * <li>**bottom**: distance to the bottom content edge of viewport element</li>
2408 * <li>**left**: distance to the left content edge of viewport element</li>
2409 * <li>**right**: distance to the right content edge of viewport element</li>
2410 * </ul>
2411 */
2412 viewportOffset: function(elem, useDocument, includePadding) {
2413 elem = this.getRawNode(elem);
2414 includePadding = includePadding !== false ? true : false;
2415
2416 var elemBCR = elem.getBoundingClientRect();
2417 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
2418
2419 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
2420 var offsetParentBCR = offsetParent.getBoundingClientRect();
2421
2422 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
2423 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
2424 if (offsetParent === $document[0].documentElement) {
2425 offsetBCR.top += $window.pageYOffset;
2426 offsetBCR.left += $window.pageXOffset;
2427 }
2428 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
2429 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
2430
2431 if (includePadding) {
2432 var offsetParentStyle = $window.getComputedStyle(offsetParent);
2433 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
2434 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
2435 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
2436 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
2437 }
2438
2439 return {
2440 top: Math.round(elemBCR.top - offsetBCR.top),
2441 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
2442 left: Math.round(elemBCR.left - offsetBCR.left),
2443 right: Math.round(offsetBCR.right - elemBCR.right)
2444 };
2445 },
2446
2447 /**
2448 * Provides an array of placement values parsed from a placement string.
2449 * Along with the 'auto' indicator, supported placement strings are:
2450 * <ul>
2451 * <li>top: element on top, horizontally centered on host element.</li>
2452 * <li>top-left: element on top, left edge aligned with host element left edge.</li>
2453 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
2454 * <li>bottom: element on bottom, horizontally centered on host element.</li>
2455 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
2456 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
2457 * <li>left: element on left, vertically centered on host element.</li>
2458 * <li>left-top: element on left, top edge aligned with host element top edge.</li>
2459 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
2460 * <li>right: element on right, vertically centered on host element.</li>
2461 * <li>right-top: element on right, top edge aligned with host element top edge.</li>
2462 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
2463 * </ul>
2464 * A placement string with an 'auto' indicator is expected to be
2465 * space separated from the placement, i.e: 'auto bottom-left' If
2466 * the primary and secondary placement values do not match 'top,
2467 * bottom, left, right' then 'top' will be the primary placement and
2468 * 'center' will be the secondary placement. If 'auto' is passed, true
2469 * will be returned as the 3rd value of the array.
2470 *
2471 * @param {string} placement - The placement string to parse.
2472 *
2473 * @returns {array} An array with the following values
2474 * <ul>
2475 * <li>**[0]**: The primary placement.</li>
2476 * <li>**[1]**: The secondary placement.</li>
2477 * <li>**[2]**: If auto is passed: true, else undefined.</li>
2478 * </ul>
2479 */
2480 parsePlacement: function(placement) {
2481 var autoPlace = PLACEMENT_REGEX.auto.test(placement);
2482 if (autoPlace) {
2483 placement = placement.replace(PLACEMENT_REGEX.auto, '');
2484 }
2485
2486 placement = placement.split('-');
2487
2488 placement[0] = placement[0] || 'top';
2489 if (!PLACEMENT_REGEX.primary.test(placement[0])) {
2490 placement[0] = 'top';
2491 }
2492
2493 placement[1] = placement[1] || 'center';
2494 if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
2495 placement[1] = 'center';
2496 }
2497
2498 if (autoPlace) {
2499 placement[2] = true;
2500 } else {
2501 placement[2] = false;
2502 }
2503
2504 return placement;
2505 },
2506
2507 /**
2508 * Provides coordinates for an element to be positioned relative to
2509 * another element. Passing 'auto' as part of the placement parameter
2510 * will enable smart placement - where the element fits. i.e:
2511 * 'auto left-top' will check to see if there is enough space to the left
2512 * of the hostElem to fit the targetElem, if not place right (same for secondary
2513 * top placement). Available space is calculated using the viewportOffset
2514 * function.
2515 *
2516 * @param {element} hostElem - The element to position against.
2517 * @param {element} targetElem - The element to position.
2518 * @param {string=} [placement=top] - The placement for the targetElem,
2519 * default is 'top'. 'center' is assumed as secondary placement for
2520 * 'top', 'left', 'right', and 'bottom' placements. Available placements are:
2521 * <ul>
2522 * <li>top</li>
2523 * <li>top-right</li>
2524 * <li>top-left</li>
2525 * <li>bottom</li>
2526 * <li>bottom-left</li>
2527 * <li>bottom-right</li>
2528 * <li>left</li>
2529 * <li>left-top</li>
2530 * <li>left-bottom</li>
2531 * <li>right</li>
2532 * <li>right-top</li>
2533 * <li>right-bottom</li>
2534 * </ul>
2535 * @param {boolean=} [appendToBody=false] - Should the top and left values returned
2536 * be calculated from the body element, default is false.
2537 *
2538 * @returns {object} An object with the following properties:
2539 * <ul>
2540 * <li>**top**: Value for targetElem top.</li>
2541 * <li>**left**: Value for targetElem left.</li>
2542 * <li>**placement**: The resolved placement.</li>
2543 * </ul>
2544 */
2545 positionElements: function(hostElem, targetElem, placement, appendToBody) {
2546 hostElem = this.getRawNode(hostElem);
2547 targetElem = this.getRawNode(targetElem);
2548
2549 // need to read from prop to support tests.
2550 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
2551 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
2552
2553 placement = this.parsePlacement(placement);
2554
2555 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
2556 var targetElemPos = {top: 0, left: 0, placement: ''};
2557
2558 if (placement[2]) {
2559 var viewportOffset = this.viewportOffset(hostElem, appendToBody);
2560
2561 var targetElemStyle = $window.getComputedStyle(targetElem);
2562 var adjustedSize = {
2563 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
2564 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
2565 };
2566
2567 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
2568 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
2569 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
2570 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
2571 placement[0];
2572
2573 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
2574 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
2575 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
2576 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
2577 placement[1];
2578
2579 if (placement[1] === 'center') {
2580 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2581 var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
2582 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
2583 placement[1] = 'left';
2584 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
2585 placement[1] = 'right';
2586 }
2587 } else {
2588 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
2589 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
2590 placement[1] = 'top';
2591 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
2592 placement[1] = 'bottom';
2593 }
2594 }
2595 }
2596 }
2597
2598 switch (placement[0]) {
2599 case 'top':
2600 targetElemPos.top = hostElemPos.top - targetHeight;
2601 break;
2602 case 'bottom':
2603 targetElemPos.top = hostElemPos.top + hostElemPos.height;
2604 break;
2605 case 'left':
2606 targetElemPos.left = hostElemPos.left - targetWidth;
2607 break;
2608 case 'right':
2609 targetElemPos.left = hostElemPos.left + hostElemPos.width;
2610 break;
2611 }
2612
2613 switch (placement[1]) {
2614 case 'top':
2615 targetElemPos.top = hostElemPos.top;
2616 break;
2617 case 'bottom':
2618 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
2619 break;
2620 case 'left':
2621 targetElemPos.left = hostElemPos.left;
2622 break;
2623 case 'right':
2624 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
2625 break;
2626 case 'center':
2627 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2628 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
2629 } else {
2630 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
2631 }
2632 break;
2633 }
2634
2635 targetElemPos.top = Math.round(targetElemPos.top);
2636 targetElemPos.left = Math.round(targetElemPos.left);
2637 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
2638
2639 return targetElemPos;
2640 },
2641
2642 /**
2643 * Provides a way to adjust the top positioning after first
2644 * render to correctly align element to top after content
2645 * rendering causes resized element height
2646 *
2647 * @param {array} placementClasses - The array of strings of classes
2648 * element should have.
2649 * @param {object} containerPosition - The object with container
2650 * position information
2651 * @param {number} initialHeight - The initial height for the elem.
2652 * @param {number} currentHeight - The current height for the elem.
2653 */
2654 adjustTop: function(placementClasses, containerPosition, initialHeight, currentHeight) {
2655 if (placementClasses.indexOf('top') !== -1 && initialHeight !== currentHeight) {
2656 return {
2657 top: containerPosition.top - currentHeight + 'px'
2658 };
2659 }
2660 },
2661
2662 /**
2663 * Provides a way for positioning tooltip & dropdown
2664 * arrows when using placement options beyond the standard
2665 * left, right, top, or bottom.
2666 *
2667 * @param {element} elem - The tooltip/dropdown element.
2668 * @param {string} placement - The placement for the elem.
2669 */
2670 positionArrow: function(elem, placement) {
2671 elem = this.getRawNode(elem);
2672
2673 var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
2674 if (!innerElem) {
2675 return;
2676 }
2677
2678 var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
2679
2680 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
2681 if (!arrowElem) {
2682 return;
2683 }
2684
2685 var arrowCss = {
2686 top: '',
2687 bottom: '',
2688 left: '',
2689 right: ''
2690 };
2691
2692 placement = this.parsePlacement(placement);
2693 if (placement[1] === 'center') {
2694 // no adjustment necessary - just reset styles
2695 angular.element(arrowElem).css(arrowCss);
2696 return;
2697 }
2698
2699 var borderProp = 'border-' + placement[0] + '-width';
2700 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
2701
2702 var borderRadiusProp = 'border-';
2703 if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2704 borderRadiusProp += placement[0] + '-' + placement[1];
2705 } else {
2706 borderRadiusProp += placement[1] + '-' + placement[0];
2707 }
2708 borderRadiusProp += '-radius';
2709 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
2710
2711 switch (placement[0]) {
2712 case 'top':
2713 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
2714 break;
2715 case 'bottom':
2716 arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
2717 break;
2718 case 'left':
2719 arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
2720 break;
2721 case 'right':
2722 arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
2723 break;
2724 }
2725
2726 arrowCss[placement[1]] = borderRadius;
2727
2728 angular.element(arrowElem).css(arrowCss);
2729 }
2730 };
2731 }]);
2732
2733angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
2734
2735.value('$datepickerPopupLiteralWarning', true)
2736
2737.constant('uibDatepickerPopupConfig', {
2738 altInputFormats: [],
2739 appendToBody: false,
2740 clearText: 'Clear',
2741 closeOnDateSelection: true,
2742 closeText: 'Done',
2743 currentText: 'Today',
2744 datepickerPopup: 'yyyy-MM-dd',
2745 datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
2746 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
2747 html5Types: {
2748 date: 'yyyy-MM-dd',
2749 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
2750 'month': 'yyyy-MM'
2751 },
2752 onOpenFocus: true,
2753 showButtonBar: true,
2754 placement: 'auto bottom-left'
2755})
2756
2757.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
2758function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
2759 var cache = {},
2760 isHtml5DateInput = false;
2761 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
2762 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
2763 ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [];
2764
2765 this.init = function(_ngModel_) {
2766 ngModel = _ngModel_;
Ed Tanous4758d5b2017-06-06 15:28:13 -07002767 ngModelOptions = extractOptions(ngModel);
Ed Tanous904063f2017-03-02 16:48:24 -08002768 closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
2769 $scope.$parent.$eval($attrs.closeOnDateSelection) :
2770 datepickerPopupConfig.closeOnDateSelection;
2771 appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
2772 $scope.$parent.$eval($attrs.datepickerAppendToBody) :
2773 datepickerPopupConfig.appendToBody;
2774 onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
2775 $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
2776 datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
2777 $attrs.datepickerPopupTemplateUrl :
2778 datepickerPopupConfig.datepickerPopupTemplateUrl;
2779 datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
2780 $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
2781 altInputFormats = angular.isDefined($attrs.altInputFormats) ?
2782 $scope.$parent.$eval($attrs.altInputFormats) :
2783 datepickerPopupConfig.altInputFormats;
2784
2785 $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
2786 $scope.$parent.$eval($attrs.showButtonBar) :
2787 datepickerPopupConfig.showButtonBar;
2788
2789 if (datepickerPopupConfig.html5Types[$attrs.type]) {
2790 dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
2791 isHtml5DateInput = true;
2792 } else {
2793 dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
2794 $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
2795 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
2796 // Invalidate the $modelValue to ensure that formatters re-run
2797 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
2798 if (newDateFormat !== dateFormat) {
2799 dateFormat = newDateFormat;
2800 ngModel.$modelValue = null;
2801
2802 if (!dateFormat) {
2803 throw new Error('uibDatepickerPopup must have a date format specified.');
2804 }
2805 }
2806 });
2807 }
2808
2809 if (!dateFormat) {
2810 throw new Error('uibDatepickerPopup must have a date format specified.');
2811 }
2812
2813 if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
2814 throw new Error('HTML5 date input types do not support custom formats.');
2815 }
2816
2817 // popup element used to display calendar
2818 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
2819
2820 popupEl.attr({
2821 'ng-model': 'date',
2822 'ng-change': 'dateSelection(date)',
2823 'template-url': datepickerPopupTemplateUrl
2824 });
2825
2826 // datepicker element
2827 datepickerEl = angular.element(popupEl.children()[0]);
2828 datepickerEl.attr('template-url', datepickerTemplateUrl);
2829
2830 if (!$scope.datepickerOptions) {
2831 $scope.datepickerOptions = {};
2832 }
2833
2834 if (isHtml5DateInput) {
2835 if ($attrs.type === 'month') {
2836 $scope.datepickerOptions.datepickerMode = 'month';
2837 $scope.datepickerOptions.minMode = 'month';
2838 }
2839 }
2840
2841 datepickerEl.attr('datepicker-options', 'datepickerOptions');
2842
2843 if (!isHtml5DateInput) {
2844 // Internal API to maintain the correct ng-invalid-[key] class
2845 ngModel.$$parserName = 'date';
2846 ngModel.$validators.date = validator;
2847 ngModel.$parsers.unshift(parseDate);
2848 ngModel.$formatters.push(function(value) {
2849 if (ngModel.$isEmpty(value)) {
2850 $scope.date = value;
2851 return value;
2852 }
2853
2854 if (angular.isNumber(value)) {
2855 value = new Date(value);
2856 }
2857
Ed Tanous4758d5b2017-06-06 15:28:13 -07002858 $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08002859
2860 return dateParser.filter($scope.date, dateFormat);
2861 });
2862 } else {
2863 ngModel.$formatters.push(function(value) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002864 $scope.date = dateParser.fromTimezone(value, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08002865 return value;
2866 });
2867 }
2868
2869 // Detect changes in the view from the text box
2870 ngModel.$viewChangeListeners.push(function() {
2871 $scope.date = parseDateString(ngModel.$viewValue);
2872 });
2873
2874 $element.on('keydown', inputKeydownBind);
2875
2876 $popup = $compile(popupEl)($scope);
2877 // Prevent jQuery cache memory leak (template is now redundant after linking)
2878 popupEl.remove();
2879
2880 if (appendToBody) {
2881 $document.find('body').append($popup);
2882 } else {
2883 $element.after($popup);
2884 }
2885
2886 $scope.$on('$destroy', function() {
2887 if ($scope.isOpen === true) {
2888 if (!$rootScope.$$phase) {
2889 $scope.$apply(function() {
2890 $scope.isOpen = false;
2891 });
2892 }
2893 }
2894
2895 $popup.remove();
2896 $element.off('keydown', inputKeydownBind);
2897 $document.off('click', documentClickBind);
2898 if (scrollParentEl) {
2899 scrollParentEl.off('scroll', positionPopup);
2900 }
2901 angular.element($window).off('resize', positionPopup);
2902
2903 //Clear all watch listeners on destroy
2904 while (watchListeners.length) {
2905 watchListeners.shift()();
2906 }
2907 });
2908 };
2909
2910 $scope.getText = function(key) {
2911 return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
2912 };
2913
2914 $scope.isDisabled = function(date) {
2915 if (date === 'today') {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002916 date = dateParser.fromTimezone(new Date(), ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08002917 }
2918
2919 var dates = {};
2920 angular.forEach(['minDate', 'maxDate'], function(key) {
2921 if (!$scope.datepickerOptions[key]) {
2922 dates[key] = null;
2923 } else if (angular.isDate($scope.datepickerOptions[key])) {
2924 dates[key] = new Date($scope.datepickerOptions[key]);
2925 } else {
2926 if ($datepickerPopupLiteralWarning) {
2927 $log.warn('Literal date support has been deprecated, please switch to date object usage');
2928 }
2929
2930 dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
2931 }
2932 });
2933
2934 return $scope.datepickerOptions &&
2935 dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
2936 dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
2937 };
2938
2939 $scope.compare = function(date1, date2) {
2940 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
2941 };
2942
2943 // Inner change
2944 $scope.dateSelection = function(dt) {
2945 $scope.date = dt;
2946 var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
2947 $element.val(date);
2948 ngModel.$setViewValue(date);
2949
2950 if (closeOnDateSelection) {
2951 $scope.isOpen = false;
2952 $element[0].focus();
2953 }
2954 };
2955
2956 $scope.keydown = function(evt) {
2957 if (evt.which === 27) {
2958 evt.stopPropagation();
2959 $scope.isOpen = false;
2960 $element[0].focus();
2961 }
2962 };
2963
2964 $scope.select = function(date, evt) {
2965 evt.stopPropagation();
2966
2967 if (date === 'today') {
2968 var today = new Date();
2969 if (angular.isDate($scope.date)) {
2970 date = new Date($scope.date);
2971 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
2972 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -07002973 date = dateParser.fromTimezone(today, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08002974 date.setHours(0, 0, 0, 0);
2975 }
2976 }
2977 $scope.dateSelection(date);
2978 };
2979
2980 $scope.close = function(evt) {
2981 evt.stopPropagation();
2982
2983 $scope.isOpen = false;
2984 $element[0].focus();
2985 };
2986
2987 $scope.disabled = angular.isDefined($attrs.disabled) || false;
2988 if ($attrs.ngDisabled) {
2989 watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
2990 $scope.disabled = disabled;
2991 }));
2992 }
2993
2994 $scope.$watch('isOpen', function(value) {
2995 if (value) {
2996 if (!$scope.disabled) {
2997 $timeout(function() {
2998 positionPopup();
2999
3000 if (onOpenFocus) {
3001 $scope.$broadcast('uib:datepicker.focus');
3002 }
3003
3004 $document.on('click', documentClickBind);
3005
3006 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
3007 if (appendToBody || $position.parsePlacement(placement)[2]) {
3008 scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
3009 if (scrollParentEl) {
3010 scrollParentEl.on('scroll', positionPopup);
3011 }
3012 } else {
3013 scrollParentEl = null;
3014 }
3015
3016 angular.element($window).on('resize', positionPopup);
3017 }, 0, false);
3018 } else {
3019 $scope.isOpen = false;
3020 }
3021 } else {
3022 $document.off('click', documentClickBind);
3023 if (scrollParentEl) {
3024 scrollParentEl.off('scroll', positionPopup);
3025 }
3026 angular.element($window).off('resize', positionPopup);
3027 }
3028 });
3029
3030 function cameltoDash(string) {
3031 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
3032 }
3033
3034 function parseDateString(viewValue) {
3035 var date = dateParser.parse(viewValue, dateFormat, $scope.date);
3036 if (isNaN(date)) {
3037 for (var i = 0; i < altInputFormats.length; i++) {
3038 date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
3039 if (!isNaN(date)) {
3040 return date;
3041 }
3042 }
3043 }
3044 return date;
3045 }
3046
3047 function parseDate(viewValue) {
3048 if (angular.isNumber(viewValue)) {
3049 // presumably timestamp to date object
3050 viewValue = new Date(viewValue);
3051 }
3052
3053 if (!viewValue) {
3054 return null;
3055 }
3056
3057 if (angular.isDate(viewValue) && !isNaN(viewValue)) {
3058 return viewValue;
3059 }
3060
3061 if (angular.isString(viewValue)) {
3062 var date = parseDateString(viewValue);
3063 if (!isNaN(date)) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003064 return dateParser.toTimezone(date, ngModelOptions.getOption('timezone'));
Ed Tanous904063f2017-03-02 16:48:24 -08003065 }
3066 }
3067
Ed Tanous4758d5b2017-06-06 15:28:13 -07003068 return ngModelOptions.getOption('allowInvalid') ? viewValue : undefined;
Ed Tanous904063f2017-03-02 16:48:24 -08003069 }
3070
3071 function validator(modelValue, viewValue) {
3072 var value = modelValue || viewValue;
3073
3074 if (!$attrs.ngRequired && !value) {
3075 return true;
3076 }
3077
3078 if (angular.isNumber(value)) {
3079 value = new Date(value);
3080 }
3081
3082 if (!value) {
3083 return true;
3084 }
3085
3086 if (angular.isDate(value) && !isNaN(value)) {
3087 return true;
3088 }
3089
3090 if (angular.isString(value)) {
3091 return !isNaN(parseDateString(value));
3092 }
3093
3094 return false;
3095 }
3096
3097 function documentClickBind(event) {
3098 if (!$scope.isOpen && $scope.disabled) {
3099 return;
3100 }
3101
3102 var popup = $popup[0];
3103 var dpContainsTarget = $element[0].contains(event.target);
3104 // The popup node may not be an element node
3105 // In some browsers (IE) only element nodes have the 'contains' function
3106 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
3107 if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
3108 $scope.$apply(function() {
3109 $scope.isOpen = false;
3110 });
3111 }
3112 }
3113
3114 function inputKeydownBind(evt) {
3115 if (evt.which === 27 && $scope.isOpen) {
3116 evt.preventDefault();
3117 evt.stopPropagation();
3118 $scope.$apply(function() {
3119 $scope.isOpen = false;
3120 });
3121 $element[0].focus();
3122 } else if (evt.which === 40 && !$scope.isOpen) {
3123 evt.preventDefault();
3124 evt.stopPropagation();
3125 $scope.$apply(function() {
3126 $scope.isOpen = true;
3127 });
3128 }
3129 }
3130
3131 function positionPopup() {
3132 if ($scope.isOpen) {
3133 var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
3134 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
3135 var position = $position.positionElements($element, dpElement, placement, appendToBody);
3136 dpElement.css({top: position.top + 'px', left: position.left + 'px'});
3137 if (dpElement.hasClass('uib-position-measure')) {
3138 dpElement.removeClass('uib-position-measure');
3139 }
3140 }
3141 }
3142
Ed Tanous4758d5b2017-06-06 15:28:13 -07003143 function extractOptions(ngModelCtrl) {
3144 var ngModelOptions;
3145
3146 if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
3147 // guarantee a value
3148 ngModelOptions = angular.isObject(ngModelCtrl.$options) ?
3149 ngModelCtrl.$options :
3150 {
3151 timezone: null
3152 };
3153
3154 // mimic 1.6+ api
3155 ngModelOptions.getOption = function (key) {
3156 return ngModelOptions[key];
3157 };
3158 } else { // in angular >=1.6 $options is always present
3159 ngModelOptions = ngModelCtrl.$options;
3160 }
3161
3162 return ngModelOptions;
3163 }
3164
Ed Tanous904063f2017-03-02 16:48:24 -08003165 $scope.$on('uib:datepicker.mode', function() {
3166 $timeout(positionPopup, 0, false);
3167 });
3168}])
3169
3170.directive('uibDatepickerPopup', function() {
3171 return {
3172 require: ['ngModel', 'uibDatepickerPopup'],
3173 controller: 'UibDatepickerPopupController',
3174 scope: {
3175 datepickerOptions: '=?',
3176 isOpen: '=?',
3177 currentText: '@',
3178 clearText: '@',
3179 closeText: '@'
3180 },
3181 link: function(scope, element, attrs, ctrls) {
3182 var ngModel = ctrls[0],
3183 ctrl = ctrls[1];
3184
3185 ctrl.init(ngModel);
3186 }
3187 };
3188})
3189
3190.directive('uibDatepickerPopupWrap', function() {
3191 return {
3192 restrict: 'A',
3193 transclude: true,
3194 templateUrl: function(element, attrs) {
3195 return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
3196 }
3197 };
3198});
3199
3200angular.module('ui.bootstrap.debounce', [])
3201/**
3202 * A helper, internal service that debounces a function
3203 */
3204 .factory('$$debounce', ['$timeout', function($timeout) {
3205 return function(callback, debounceTime) {
3206 var timeoutPromise;
3207
3208 return function() {
3209 var self = this;
3210 var args = Array.prototype.slice.call(arguments);
3211 if (timeoutPromise) {
3212 $timeout.cancel(timeoutPromise);
3213 }
3214
3215 timeoutPromise = $timeout(function() {
3216 callback.apply(self, args);
3217 }, debounceTime);
3218 };
3219 };
3220 }]);
3221
Ed Tanous4758d5b2017-06-06 15:28:13 -07003222angular.module('ui.bootstrap.multiMap', [])
3223/**
3224 * A helper, internal data structure that stores all references attached to key
3225 */
3226 .factory('$$multiMap', function() {
3227 return {
3228 createNew: function() {
3229 var map = {};
3230
3231 return {
3232 entries: function() {
3233 return Object.keys(map).map(function(key) {
3234 return {
3235 key: key,
3236 value: map[key]
3237 };
3238 });
3239 },
3240 get: function(key) {
3241 return map[key];
3242 },
3243 hasKey: function(key) {
3244 return !!map[key];
3245 },
3246 keys: function() {
3247 return Object.keys(map);
3248 },
3249 put: function(key, value) {
3250 if (!map[key]) {
3251 map[key] = [];
3252 }
3253
3254 map[key].push(value);
3255 },
3256 remove: function(key, value) {
3257 var values = map[key];
3258
3259 if (!values) {
3260 return;
3261 }
3262
3263 var idx = values.indexOf(value);
3264
3265 if (idx !== -1) {
3266 values.splice(idx, 1);
3267 }
3268
3269 if (!values.length) {
3270 delete map[key];
3271 }
3272 }
3273 };
3274 }
3275 };
3276 });
3277
3278angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.multiMap', 'ui.bootstrap.position'])
Ed Tanous904063f2017-03-02 16:48:24 -08003279
3280.constant('uibDropdownConfig', {
3281 appendToOpenClass: 'uib-dropdown-open',
3282 openClass: 'open'
3283})
3284
Ed Tanous4758d5b2017-06-06 15:28:13 -07003285.service('uibDropdownService', ['$document', '$rootScope', '$$multiMap', function($document, $rootScope, $$multiMap) {
Ed Tanous904063f2017-03-02 16:48:24 -08003286 var openScope = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003287 var openedContainers = $$multiMap.createNew();
Ed Tanous904063f2017-03-02 16:48:24 -08003288
Ed Tanous4758d5b2017-06-06 15:28:13 -07003289 this.isOnlyOpen = function(dropdownScope, appendTo) {
3290 var openedDropdowns = openedContainers.get(appendTo);
3291 if (openedDropdowns) {
3292 var openDropdown = openedDropdowns.reduce(function(toClose, dropdown) {
3293 if (dropdown.scope === dropdownScope) {
3294 return dropdown;
3295 }
3296
3297 return toClose;
3298 }, {});
3299 if (openDropdown) {
3300 return openedDropdowns.length === 1;
3301 }
3302 }
3303
3304 return false;
3305 };
3306
3307 this.open = function(dropdownScope, element, appendTo) {
Ed Tanous904063f2017-03-02 16:48:24 -08003308 if (!openScope) {
3309 $document.on('click', closeDropdown);
3310 }
3311
3312 if (openScope && openScope !== dropdownScope) {
3313 openScope.isOpen = false;
3314 }
3315
3316 openScope = dropdownScope;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003317
3318 if (!appendTo) {
3319 return;
3320 }
3321
3322 var openedDropdowns = openedContainers.get(appendTo);
3323 if (openedDropdowns) {
3324 var openedScopes = openedDropdowns.map(function(dropdown) {
3325 return dropdown.scope;
3326 });
3327 if (openedScopes.indexOf(dropdownScope) === -1) {
3328 openedContainers.put(appendTo, {
3329 scope: dropdownScope
3330 });
3331 }
3332 } else {
3333 openedContainers.put(appendTo, {
3334 scope: dropdownScope
3335 });
3336 }
Ed Tanous904063f2017-03-02 16:48:24 -08003337 };
3338
Ed Tanous4758d5b2017-06-06 15:28:13 -07003339 this.close = function(dropdownScope, element, appendTo) {
Ed Tanous904063f2017-03-02 16:48:24 -08003340 if (openScope === dropdownScope) {
Ed Tanous904063f2017-03-02 16:48:24 -08003341 $document.off('click', closeDropdown);
3342 $document.off('keydown', this.keybindFilter);
Ed Tanous4758d5b2017-06-06 15:28:13 -07003343 openScope = null;
3344 }
3345
3346 if (!appendTo) {
3347 return;
3348 }
3349
3350 var openedDropdowns = openedContainers.get(appendTo);
3351 if (openedDropdowns) {
3352 var dropdownToClose = openedDropdowns.reduce(function(toClose, dropdown) {
3353 if (dropdown.scope === dropdownScope) {
3354 return dropdown;
3355 }
3356
3357 return toClose;
3358 }, {});
3359 if (dropdownToClose) {
3360 openedContainers.remove(appendTo, dropdownToClose);
3361 }
Ed Tanous904063f2017-03-02 16:48:24 -08003362 }
3363 };
3364
3365 var closeDropdown = function(evt) {
3366 // This method may still be called during the same mouse event that
3367 // unbound this event handler. So check openScope before proceeding.
Ed Tanous4758d5b2017-06-06 15:28:13 -07003368 if (!openScope || !openScope.isOpen) { return; }
Ed Tanous904063f2017-03-02 16:48:24 -08003369
3370 if (evt && openScope.getAutoClose() === 'disabled') { return; }
3371
3372 if (evt && evt.which === 3) { return; }
3373
3374 var toggleElement = openScope.getToggleElement();
3375 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
3376 return;
3377 }
3378
3379 var dropdownElement = openScope.getDropdownElement();
3380 if (evt && openScope.getAutoClose() === 'outsideClick' &&
3381 dropdownElement && dropdownElement[0].contains(evt.target)) {
3382 return;
3383 }
3384
Ed Tanous904063f2017-03-02 16:48:24 -08003385 openScope.focusToggleElement();
Ed Tanous4758d5b2017-06-06 15:28:13 -07003386 openScope.isOpen = false;
Ed Tanous904063f2017-03-02 16:48:24 -08003387
3388 if (!$rootScope.$$phase) {
3389 openScope.$apply();
3390 }
3391 };
3392
3393 this.keybindFilter = function(evt) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003394 if (!openScope) {
3395 // see this.close as ESC could have been pressed which kills the scope so we can not proceed
3396 return;
3397 }
3398
Ed Tanous904063f2017-03-02 16:48:24 -08003399 var dropdownElement = openScope.getDropdownElement();
3400 var toggleElement = openScope.getToggleElement();
3401 var dropdownElementTargeted = dropdownElement && dropdownElement[0].contains(evt.target);
3402 var toggleElementTargeted = toggleElement && toggleElement[0].contains(evt.target);
3403 if (evt.which === 27) {
3404 evt.stopPropagation();
3405 openScope.focusToggleElement();
3406 closeDropdown();
3407 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen && (dropdownElementTargeted || toggleElementTargeted)) {
3408 evt.preventDefault();
3409 evt.stopPropagation();
3410 openScope.focusDropdownEntry(evt.which);
3411 }
3412 };
3413}])
3414
3415.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
3416 var self = this,
3417 scope = $scope.$new(), // create a child scope so we are not polluting original one
3418 templateScope,
3419 appendToOpenClass = dropdownConfig.appendToOpenClass,
3420 openClass = dropdownConfig.openClass,
3421 getIsOpen,
3422 setIsOpen = angular.noop,
3423 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
Ed Tanous904063f2017-03-02 16:48:24 -08003424 keynavEnabled = false,
3425 selectedOption = null,
3426 body = $document.find('body');
3427
3428 $element.addClass('dropdown');
3429
3430 this.init = function() {
3431 if ($attrs.isOpen) {
3432 getIsOpen = $parse($attrs.isOpen);
3433 setIsOpen = getIsOpen.assign;
3434
3435 $scope.$watch(getIsOpen, function(value) {
3436 scope.isOpen = !!value;
3437 });
3438 }
3439
Ed Tanous904063f2017-03-02 16:48:24 -08003440 keynavEnabled = angular.isDefined($attrs.keyboardNav);
Ed Tanous904063f2017-03-02 16:48:24 -08003441 };
3442
3443 this.toggle = function(open) {
3444 scope.isOpen = arguments.length ? !!open : !scope.isOpen;
3445 if (angular.isFunction(setIsOpen)) {
3446 setIsOpen(scope, scope.isOpen);
3447 }
3448
3449 return scope.isOpen;
3450 };
3451
3452 // Allow other directives to watch status
3453 this.isOpen = function() {
3454 return scope.isOpen;
3455 };
3456
3457 scope.getToggleElement = function() {
3458 return self.toggleElement;
3459 };
3460
3461 scope.getAutoClose = function() {
3462 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
3463 };
3464
3465 scope.getElement = function() {
3466 return $element;
3467 };
3468
3469 scope.isKeynavEnabled = function() {
3470 return keynavEnabled;
3471 };
3472
3473 scope.focusDropdownEntry = function(keyCode) {
3474 var elems = self.dropdownMenu ? //If append to body is used.
3475 angular.element(self.dropdownMenu).find('a') :
3476 $element.find('ul').eq(0).find('a');
3477
3478 switch (keyCode) {
3479 case 40: {
3480 if (!angular.isNumber(self.selectedOption)) {
3481 self.selectedOption = 0;
3482 } else {
3483 self.selectedOption = self.selectedOption === elems.length - 1 ?
3484 self.selectedOption :
3485 self.selectedOption + 1;
3486 }
3487 break;
3488 }
3489 case 38: {
3490 if (!angular.isNumber(self.selectedOption)) {
3491 self.selectedOption = elems.length - 1;
3492 } else {
3493 self.selectedOption = self.selectedOption === 0 ?
3494 0 : self.selectedOption - 1;
3495 }
3496 break;
3497 }
3498 }
3499 elems[self.selectedOption].focus();
3500 };
3501
3502 scope.getDropdownElement = function() {
3503 return self.dropdownMenu;
3504 };
3505
3506 scope.focusToggleElement = function() {
3507 if (self.toggleElement) {
3508 self.toggleElement[0].focus();
3509 }
3510 };
3511
Ed Tanous4758d5b2017-06-06 15:28:13 -07003512 function removeDropdownMenu() {
3513 $element.append(self.dropdownMenu);
3514 }
3515
Ed Tanous904063f2017-03-02 16:48:24 -08003516 scope.$watch('isOpen', function(isOpen, wasOpen) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003517 var appendTo = null,
3518 appendToBody = false;
3519
3520 if (angular.isDefined($attrs.dropdownAppendTo)) {
3521 var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
3522 if (appendToEl) {
3523 appendTo = angular.element(appendToEl);
3524 }
3525 }
3526
3527 if (angular.isDefined($attrs.dropdownAppendToBody)) {
3528 var appendToBodyValue = $parse($attrs.dropdownAppendToBody)(scope);
3529 if (appendToBodyValue !== false) {
3530 appendToBody = true;
3531 }
3532 }
3533
3534 if (appendToBody && !appendTo) {
3535 appendTo = body;
3536 }
3537
3538 if (appendTo && self.dropdownMenu) {
3539 if (isOpen) {
3540 appendTo.append(self.dropdownMenu);
3541 $element.on('$destroy', removeDropdownMenu);
3542 } else {
3543 $element.off('$destroy', removeDropdownMenu);
3544 removeDropdownMenu();
3545 }
3546 }
3547
Ed Tanous904063f2017-03-02 16:48:24 -08003548 if (appendTo && self.dropdownMenu) {
3549 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
3550 css,
3551 rightalign,
3552 scrollbarPadding,
3553 scrollbarWidth = 0;
3554
3555 css = {
3556 top: pos.top + 'px',
3557 display: isOpen ? 'block' : 'none'
3558 };
3559
3560 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
3561 if (!rightalign) {
3562 css.left = pos.left + 'px';
3563 css.right = 'auto';
3564 } else {
3565 css.left = 'auto';
3566 scrollbarPadding = $position.scrollbarPadding(appendTo);
3567
3568 if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
3569 scrollbarWidth = scrollbarPadding.scrollbarWidth;
3570 }
3571
3572 css.right = window.innerWidth - scrollbarWidth -
3573 (pos.left + $element.prop('offsetWidth')) + 'px';
3574 }
3575
3576 // Need to adjust our positioning to be relative to the appendTo container
3577 // if it's not the body element
3578 if (!appendToBody) {
3579 var appendOffset = $position.offset(appendTo);
3580
3581 css.top = pos.top - appendOffset.top + 'px';
3582
3583 if (!rightalign) {
3584 css.left = pos.left - appendOffset.left + 'px';
3585 } else {
3586 css.right = window.innerWidth -
3587 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
3588 }
3589 }
3590
3591 self.dropdownMenu.css(css);
3592 }
3593
3594 var openContainer = appendTo ? appendTo : $element;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003595 var dropdownOpenClass = appendTo ? appendToOpenClass : openClass;
3596 var hasOpenClass = openContainer.hasClass(dropdownOpenClass);
3597 var isOnlyOpen = uibDropdownService.isOnlyOpen($scope, appendTo);
Ed Tanous904063f2017-03-02 16:48:24 -08003598
3599 if (hasOpenClass === !isOpen) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003600 var toggleClass;
3601 if (appendTo) {
3602 toggleClass = !isOnlyOpen ? 'addClass' : 'removeClass';
3603 } else {
3604 toggleClass = isOpen ? 'addClass' : 'removeClass';
3605 }
3606 $animate[toggleClass](openContainer, dropdownOpenClass).then(function() {
Ed Tanous904063f2017-03-02 16:48:24 -08003607 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
3608 toggleInvoker($scope, { open: !!isOpen });
3609 }
3610 });
3611 }
3612
3613 if (isOpen) {
3614 if (self.dropdownMenuTemplateUrl) {
3615 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
3616 templateScope = scope.$new();
3617 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
3618 var newEl = dropdownElement;
3619 self.dropdownMenu.replaceWith(newEl);
3620 self.dropdownMenu = newEl;
3621 $document.on('keydown', uibDropdownService.keybindFilter);
3622 });
3623 });
3624 } else {
3625 $document.on('keydown', uibDropdownService.keybindFilter);
3626 }
3627
3628 scope.focusToggleElement();
Ed Tanous4758d5b2017-06-06 15:28:13 -07003629 uibDropdownService.open(scope, $element, appendTo);
Ed Tanous904063f2017-03-02 16:48:24 -08003630 } else {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003631 uibDropdownService.close(scope, $element, appendTo);
Ed Tanous904063f2017-03-02 16:48:24 -08003632 if (self.dropdownMenuTemplateUrl) {
3633 if (templateScope) {
3634 templateScope.$destroy();
3635 }
3636 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
3637 self.dropdownMenu.replaceWith(newEl);
3638 self.dropdownMenu = newEl;
3639 }
3640
3641 self.selectedOption = null;
3642 }
3643
3644 if (angular.isFunction(setIsOpen)) {
3645 setIsOpen($scope, isOpen);
3646 }
3647 });
3648}])
3649
3650.directive('uibDropdown', function() {
3651 return {
3652 controller: 'UibDropdownController',
3653 link: function(scope, element, attrs, dropdownCtrl) {
3654 dropdownCtrl.init();
3655 }
3656 };
3657})
3658
3659.directive('uibDropdownMenu', function() {
3660 return {
3661 restrict: 'A',
3662 require: '?^uibDropdown',
3663 link: function(scope, element, attrs, dropdownCtrl) {
3664 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
3665 return;
3666 }
3667
3668 element.addClass('dropdown-menu');
3669
3670 var tplUrl = attrs.templateUrl;
3671 if (tplUrl) {
3672 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
3673 }
3674
3675 if (!dropdownCtrl.dropdownMenu) {
3676 dropdownCtrl.dropdownMenu = element;
3677 }
3678 }
3679 };
3680})
3681
3682.directive('uibDropdownToggle', function() {
3683 return {
3684 require: '?^uibDropdown',
3685 link: function(scope, element, attrs, dropdownCtrl) {
3686 if (!dropdownCtrl) {
3687 return;
3688 }
3689
3690 element.addClass('dropdown-toggle');
3691
3692 dropdownCtrl.toggleElement = element;
3693
3694 var toggleDropdown = function(event) {
3695 event.preventDefault();
3696
3697 if (!element.hasClass('disabled') && !attrs.disabled) {
3698 scope.$apply(function() {
3699 dropdownCtrl.toggle();
3700 });
3701 }
3702 };
3703
Ed Tanous4758d5b2017-06-06 15:28:13 -07003704 element.on('click', toggleDropdown);
Ed Tanous904063f2017-03-02 16:48:24 -08003705
3706 // WAI-ARIA
3707 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
3708 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
3709 element.attr('aria-expanded', !!isOpen);
3710 });
3711
3712 scope.$on('$destroy', function() {
Ed Tanous4758d5b2017-06-06 15:28:13 -07003713 element.off('click', toggleDropdown);
Ed Tanous904063f2017-03-02 16:48:24 -08003714 });
3715 }
3716 };
3717});
3718
3719angular.module('ui.bootstrap.stackedMap', [])
3720/**
3721 * A helper, internal data structure that acts as a map but also allows getting / removing
3722 * elements in the LIFO order
3723 */
3724 .factory('$$stackedMap', function() {
3725 return {
3726 createNew: function() {
3727 var stack = [];
3728
3729 return {
3730 add: function(key, value) {
3731 stack.push({
3732 key: key,
3733 value: value
3734 });
3735 },
3736 get: function(key) {
3737 for (var i = 0; i < stack.length; i++) {
3738 if (key === stack[i].key) {
3739 return stack[i];
3740 }
3741 }
3742 },
3743 keys: function() {
3744 var keys = [];
3745 for (var i = 0; i < stack.length; i++) {
3746 keys.push(stack[i].key);
3747 }
3748 return keys;
3749 },
3750 top: function() {
3751 return stack[stack.length - 1];
3752 },
3753 remove: function(key) {
3754 var idx = -1;
3755 for (var i = 0; i < stack.length; i++) {
3756 if (key === stack[i].key) {
3757 idx = i;
3758 break;
3759 }
3760 }
3761 return stack.splice(idx, 1)[0];
3762 },
3763 removeTop: function() {
3764 return stack.pop();
3765 },
3766 length: function() {
3767 return stack.length;
3768 }
3769 };
3770 }
3771 };
3772 });
Ed Tanous4758d5b2017-06-06 15:28:13 -07003773angular.module('ui.bootstrap.modal', ['ui.bootstrap.multiMap', 'ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
Ed Tanous904063f2017-03-02 16:48:24 -08003774/**
3775 * Pluggable resolve mechanism for the modal resolve resolution
3776 * Supports UI Router's $resolve service
3777 */
3778 .provider('$uibResolve', function() {
3779 var resolve = this;
3780 this.resolver = null;
3781
3782 this.setResolver = function(resolver) {
3783 this.resolver = resolver;
3784 };
3785
3786 this.$get = ['$injector', '$q', function($injector, $q) {
3787 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
3788 return {
3789 resolve: function(invocables, locals, parent, self) {
3790 if (resolver) {
3791 return resolver.resolve(invocables, locals, parent, self);
3792 }
3793
3794 var promises = [];
3795
3796 angular.forEach(invocables, function(value) {
3797 if (angular.isFunction(value) || angular.isArray(value)) {
3798 promises.push($q.resolve($injector.invoke(value)));
3799 } else if (angular.isString(value)) {
3800 promises.push($q.resolve($injector.get(value)));
3801 } else {
3802 promises.push($q.resolve(value));
3803 }
3804 });
3805
3806 return $q.all(promises).then(function(resolves) {
3807 var resolveObj = {};
3808 var resolveIter = 0;
3809 angular.forEach(invocables, function(value, key) {
3810 resolveObj[key] = resolves[resolveIter++];
3811 });
3812
3813 return resolveObj;
3814 });
3815 }
3816 };
3817 }];
3818 })
3819
3820/**
3821 * A helper directive for the $modal service. It creates a backdrop element.
3822 */
3823 .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
3824 function($animate, $injector, $modalStack) {
3825 return {
3826 restrict: 'A',
3827 compile: function(tElement, tAttrs) {
3828 tElement.addClass(tAttrs.backdropClass);
3829 return linkFn;
3830 }
3831 };
3832
3833 function linkFn(scope, element, attrs) {
3834 if (attrs.modalInClass) {
3835 $animate.addClass(element, attrs.modalInClass);
3836
3837 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3838 var done = setIsAsync();
3839 if (scope.modalOptions.animation) {
3840 $animate.removeClass(element, attrs.modalInClass).then(done);
3841 } else {
3842 done();
3843 }
3844 });
3845 }
3846 }
3847 }])
3848
3849 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
3850 function($modalStack, $q, $animateCss, $document) {
3851 return {
3852 scope: {
3853 index: '@'
3854 },
3855 restrict: 'A',
3856 transclude: true,
3857 templateUrl: function(tElement, tAttrs) {
3858 return tAttrs.templateUrl || 'uib/template/modal/window.html';
3859 },
3860 link: function(scope, element, attrs) {
3861 element.addClass(attrs.windowTopClass || '');
3862 scope.size = attrs.size;
3863
3864 scope.close = function(evt) {
3865 var modal = $modalStack.getTop();
3866 if (modal && modal.value.backdrop &&
3867 modal.value.backdrop !== 'static' &&
3868 evt.target === evt.currentTarget) {
3869 evt.preventDefault();
3870 evt.stopPropagation();
3871 $modalStack.dismiss(modal.key, 'backdrop click');
3872 }
3873 };
3874
3875 // moved from template to fix issue #2280
3876 element.on('click', scope.close);
3877
3878 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
3879 // We can detect that by using this property in the template associated with this directive and then use
3880 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
3881 scope.$isRendered = true;
3882
Ed Tanous4758d5b2017-06-06 15:28:13 -07003883 // Deferred object that will be resolved when this modal is rendered.
Ed Tanous904063f2017-03-02 16:48:24 -08003884 var modalRenderDeferObj = $q.defer();
3885 // Resolve render promise post-digest
3886 scope.$$postDigest(function() {
3887 modalRenderDeferObj.resolve();
3888 });
3889
3890 modalRenderDeferObj.promise.then(function() {
3891 var animationPromise = null;
3892
3893 if (attrs.modalInClass) {
3894 animationPromise = $animateCss(element, {
3895 addClass: attrs.modalInClass
3896 }).start();
3897
3898 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3899 var done = setIsAsync();
3900 $animateCss(element, {
3901 removeClass: attrs.modalInClass
3902 }).start().then(done);
3903 });
3904 }
3905
3906
3907 $q.when(animationPromise).then(function() {
3908 // Notify {@link $modalStack} that modal is rendered.
3909 var modal = $modalStack.getTop();
3910 if (modal) {
3911 $modalStack.modalRendered(modal.key);
3912 }
3913
3914 /**
3915 * If something within the freshly-opened modal already has focus (perhaps via a
Ed Tanous4758d5b2017-06-06 15:28:13 -07003916 * directive that causes focus) then there's no need to try to focus anything.
Ed Tanous904063f2017-03-02 16:48:24 -08003917 */
3918 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
3919 var inputWithAutofocus = element[0].querySelector('[autofocus]');
3920 /**
3921 * Auto-focusing of a freshly-opened modal element causes any child elements
3922 * with the autofocus attribute to lose focus. This is an issue on touch
3923 * based devices which will show and then hide the onscreen keyboard.
3924 * Attempts to refocus the autofocus element via JavaScript will not reopen
3925 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
3926 * the modal element if the modal does not contain an autofocus element.
3927 */
3928 if (inputWithAutofocus) {
3929 inputWithAutofocus.focus();
3930 } else {
3931 element[0].focus();
3932 }
3933 }
3934 });
3935 });
3936 }
3937 };
3938 }])
3939
3940 .directive('uibModalAnimationClass', function() {
3941 return {
3942 compile: function(tElement, tAttrs) {
3943 if (tAttrs.modalAnimation) {
3944 tElement.addClass(tAttrs.uibModalAnimationClass);
3945 }
3946 }
3947 };
3948 })
3949
3950 .directive('uibModalTransclude', ['$animate', function($animate) {
3951 return {
3952 link: function(scope, element, attrs, controller, transclude) {
3953 transclude(scope.$parent, function(clone) {
3954 element.empty();
3955 $animate.enter(clone, element);
3956 });
3957 }
3958 };
3959 }])
3960
3961 .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
3962 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
3963 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
3964 var OPENED_MODAL_CLASS = 'modal-open';
3965
3966 var backdropDomEl, backdropScope;
3967 var openedWindows = $$stackedMap.createNew();
3968 var openedClasses = $$multiMap.createNew();
3969 var $modalStack = {
3970 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
3971 };
3972 var topModalIndex = 0;
3973 var previousTopOpenedModal = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07003974 var ARIA_HIDDEN_ATTRIBUTE_NAME = 'data-bootstrap-modal-aria-hidden-count';
Ed Tanous904063f2017-03-02 16:48:24 -08003975
3976 //Modal focus behavior
3977 var tabbableSelector = 'a[href], area[href], input:not([disabled]):not([tabindex=\'-1\']), ' +
3978 'button:not([disabled]):not([tabindex=\'-1\']),select:not([disabled]):not([tabindex=\'-1\']), textarea:not([disabled]):not([tabindex=\'-1\']), ' +
3979 'iframe, object, embed, *[tabindex]:not([tabindex=\'-1\']), *[contenteditable=true]';
3980 var scrollbarPadding;
3981 var SNAKE_CASE_REGEXP = /[A-Z]/g;
3982
3983 // TODO: extract into common dependency with tooltip
3984 function snake_case(name) {
3985 var separator = '-';
3986 return name.replace(SNAKE_CASE_REGEXP, function(letter, pos) {
3987 return (pos ? separator : '') + letter.toLowerCase();
3988 });
3989 }
3990
3991 function isVisible(element) {
3992 return !!(element.offsetWidth ||
3993 element.offsetHeight ||
3994 element.getClientRects().length);
3995 }
3996
3997 function backdropIndex() {
3998 var topBackdropIndex = -1;
3999 var opened = openedWindows.keys();
4000 for (var i = 0; i < opened.length; i++) {
4001 if (openedWindows.get(opened[i]).value.backdrop) {
4002 topBackdropIndex = i;
4003 }
4004 }
4005
4006 // If any backdrop exist, ensure that it's index is always
4007 // right below the top modal
4008 if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
4009 topBackdropIndex = topModalIndex;
4010 }
4011 return topBackdropIndex;
4012 }
4013
4014 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
4015 if (backdropScope) {
4016 backdropScope.index = newBackdropIndex;
4017 }
4018 });
4019
4020 function removeModalWindow(modalInstance, elementToReceiveFocus) {
4021 var modalWindow = openedWindows.get(modalInstance).value;
4022 var appendToElement = modalWindow.appendTo;
4023
4024 //clean up the stack
4025 openedWindows.remove(modalInstance);
4026 previousTopOpenedModal = openedWindows.top();
4027 if (previousTopOpenedModal) {
4028 topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
4029 }
4030
4031 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
4032 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
4033 openedClasses.remove(modalBodyClass, modalInstance);
4034 var areAnyOpen = openedClasses.hasKey(modalBodyClass);
4035 appendToElement.toggleClass(modalBodyClass, areAnyOpen);
4036 if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
4037 if (scrollbarPadding.originalRight) {
4038 appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
4039 } else {
4040 appendToElement.css({paddingRight: ''});
4041 }
4042 scrollbarPadding = null;
4043 }
4044 toggleTopWindowClass(true);
4045 }, modalWindow.closedDeferred);
4046 checkRemoveBackdrop();
4047
4048 //move focus to specified element if available, or else to body
4049 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
4050 elementToReceiveFocus.focus();
4051 } else if (appendToElement.focus) {
4052 appendToElement.focus();
4053 }
4054 }
4055
4056 // Add or remove "windowTopClass" from the top window in the stack
4057 function toggleTopWindowClass(toggleSwitch) {
4058 var modalWindow;
4059
4060 if (openedWindows.length() > 0) {
4061 modalWindow = openedWindows.top().value;
4062 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
4063 }
4064 }
4065
4066 function checkRemoveBackdrop() {
4067 //remove backdrop if no longer needed
4068 if (backdropDomEl && backdropIndex() === -1) {
4069 var backdropScopeRef = backdropScope;
4070 removeAfterAnimate(backdropDomEl, backdropScope, function() {
4071 backdropScopeRef = null;
4072 });
4073 backdropDomEl = undefined;
4074 backdropScope = undefined;
4075 }
4076 }
4077
4078 function removeAfterAnimate(domEl, scope, done, closedDeferred) {
4079 var asyncDeferred;
4080 var asyncPromise = null;
4081 var setIsAsync = function() {
4082 if (!asyncDeferred) {
4083 asyncDeferred = $q.defer();
4084 asyncPromise = asyncDeferred.promise;
4085 }
4086
4087 return function asyncDone() {
4088 asyncDeferred.resolve();
4089 };
4090 };
4091 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
4092
4093 // Note that it's intentional that asyncPromise might be null.
4094 // That's when setIsAsync has not been called during the
4095 // NOW_CLOSING_EVENT broadcast.
4096 return $q.when(asyncPromise).then(afterAnimating);
4097
4098 function afterAnimating() {
4099 if (afterAnimating.done) {
4100 return;
4101 }
4102 afterAnimating.done = true;
4103
4104 $animate.leave(domEl).then(function() {
4105 if (done) {
4106 done();
4107 }
4108
4109 domEl.remove();
4110 if (closedDeferred) {
4111 closedDeferred.resolve();
4112 }
4113 });
4114
4115 scope.$destroy();
4116 }
4117 }
4118
4119 $document.on('keydown', keydownListener);
4120
4121 $rootScope.$on('$destroy', function() {
4122 $document.off('keydown', keydownListener);
4123 });
4124
4125 function keydownListener(evt) {
4126 if (evt.isDefaultPrevented()) {
4127 return evt;
4128 }
4129
4130 var modal = openedWindows.top();
4131 if (modal) {
4132 switch (evt.which) {
4133 case 27: {
4134 if (modal.value.keyboard) {
4135 evt.preventDefault();
4136 $rootScope.$apply(function() {
4137 $modalStack.dismiss(modal.key, 'escape key press');
4138 });
4139 }
4140 break;
4141 }
4142 case 9: {
4143 var list = $modalStack.loadFocusElementList(modal);
4144 var focusChanged = false;
4145 if (evt.shiftKey) {
4146 if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
4147 focusChanged = $modalStack.focusLastFocusableElement(list);
4148 }
4149 } else {
4150 if ($modalStack.isFocusInLastItem(evt, list)) {
4151 focusChanged = $modalStack.focusFirstFocusableElement(list);
4152 }
4153 }
4154
4155 if (focusChanged) {
4156 evt.preventDefault();
4157 evt.stopPropagation();
4158 }
4159
4160 break;
4161 }
4162 }
4163 }
4164 }
4165
4166 $modalStack.open = function(modalInstance, modal) {
4167 var modalOpener = $document[0].activeElement,
4168 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
4169
4170 toggleTopWindowClass(false);
4171
4172 // Store the current top first, to determine what index we ought to use
4173 // for the current top modal
4174 previousTopOpenedModal = openedWindows.top();
4175
4176 openedWindows.add(modalInstance, {
4177 deferred: modal.deferred,
4178 renderDeferred: modal.renderDeferred,
4179 closedDeferred: modal.closedDeferred,
4180 modalScope: modal.scope,
4181 backdrop: modal.backdrop,
4182 keyboard: modal.keyboard,
4183 openedClass: modal.openedClass,
4184 windowTopClass: modal.windowTopClass,
4185 animation: modal.animation,
4186 appendTo: modal.appendTo
4187 });
4188
4189 openedClasses.put(modalBodyClass, modalInstance);
4190
4191 var appendToElement = modal.appendTo,
4192 currBackdropIndex = backdropIndex();
4193
Ed Tanous904063f2017-03-02 16:48:24 -08004194 if (currBackdropIndex >= 0 && !backdropDomEl) {
4195 backdropScope = $rootScope.$new(true);
4196 backdropScope.modalOptions = modal;
4197 backdropScope.index = currBackdropIndex;
4198 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
4199 backdropDomEl.attr({
4200 'class': 'modal-backdrop',
4201 'ng-style': '{\'z-index\': 1040 + (index && 1 || 0) + index*10}',
4202 'uib-modal-animation-class': 'fade',
4203 'modal-in-class': 'in'
4204 });
4205 if (modal.backdropClass) {
4206 backdropDomEl.addClass(modal.backdropClass);
4207 }
4208
4209 if (modal.animation) {
4210 backdropDomEl.attr('modal-animation', 'true');
4211 }
4212 $compile(backdropDomEl)(backdropScope);
4213 $animate.enter(backdropDomEl, appendToElement);
4214 if ($uibPosition.isScrollable(appendToElement)) {
4215 scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
4216 if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
4217 appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
4218 }
4219 }
4220 }
4221
4222 var content;
4223 if (modal.component) {
4224 content = document.createElement(snake_case(modal.component.name));
4225 content = angular.element(content);
4226 content.attr({
4227 resolve: '$resolve',
4228 'modal-instance': '$uibModalInstance',
4229 close: '$close($value)',
4230 dismiss: '$dismiss($value)'
4231 });
4232 } else {
4233 content = modal.content;
4234 }
4235
4236 // Set the top modal index based on the index of the previous top modal
4237 topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
4238 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
4239 angularDomEl.attr({
4240 'class': 'modal',
4241 'template-url': modal.windowTemplateUrl,
4242 'window-top-class': modal.windowTopClass,
4243 'role': 'dialog',
4244 'aria-labelledby': modal.ariaLabelledBy,
4245 'aria-describedby': modal.ariaDescribedBy,
4246 'size': modal.size,
4247 'index': topModalIndex,
4248 'animate': 'animate',
4249 'ng-style': '{\'z-index\': 1050 + $$topModalIndex*10, display: \'block\'}',
4250 'tabindex': -1,
4251 'uib-modal-animation-class': 'fade',
4252 'modal-in-class': 'in'
4253 }).append(content);
4254 if (modal.windowClass) {
4255 angularDomEl.addClass(modal.windowClass);
4256 }
4257
4258 if (modal.animation) {
4259 angularDomEl.attr('modal-animation', 'true');
4260 }
4261
4262 appendToElement.addClass(modalBodyClass);
4263 if (modal.scope) {
4264 // we need to explicitly add the modal index to the modal scope
4265 // because it is needed by ngStyle to compute the zIndex property.
4266 modal.scope.$$topModalIndex = topModalIndex;
4267 }
4268 $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
4269
4270 openedWindows.top().value.modalDomEl = angularDomEl;
4271 openedWindows.top().value.modalOpener = modalOpener;
Ed Tanous4758d5b2017-06-06 15:28:13 -07004272
4273 applyAriaHidden(angularDomEl);
4274
4275 function applyAriaHidden(el) {
4276 if (!el || el[0].tagName === 'BODY') {
4277 return;
4278 }
4279
4280 getSiblings(el).forEach(function(sibling) {
4281 var elemIsAlreadyHidden = sibling.getAttribute('aria-hidden') === 'true',
4282 ariaHiddenCount = parseInt(sibling.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10);
4283
4284 if (!ariaHiddenCount) {
4285 ariaHiddenCount = elemIsAlreadyHidden ? 1 : 0;
4286 }
4287
4288 sibling.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, ariaHiddenCount + 1);
4289 sibling.setAttribute('aria-hidden', 'true');
4290 });
4291
4292 return applyAriaHidden(el.parent());
4293
4294 function getSiblings(el) {
4295 var children = el.parent() ? el.parent().children() : [];
4296
4297 return Array.prototype.filter.call(children, function(child) {
4298 return child !== el[0];
4299 });
4300 }
4301 }
Ed Tanous904063f2017-03-02 16:48:24 -08004302 };
4303
4304 function broadcastClosing(modalWindow, resultOrReason, closing) {
4305 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
4306 }
4307
Ed Tanous4758d5b2017-06-06 15:28:13 -07004308 function unhideBackgroundElements() {
4309 Array.prototype.forEach.call(
4310 document.querySelectorAll('[' + ARIA_HIDDEN_ATTRIBUTE_NAME + ']'),
4311 function(hiddenEl) {
4312 var ariaHiddenCount = parseInt(hiddenEl.getAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME), 10),
4313 newHiddenCount = ariaHiddenCount - 1;
4314 hiddenEl.setAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME, newHiddenCount);
4315
4316 if (!newHiddenCount) {
4317 hiddenEl.removeAttribute(ARIA_HIDDEN_ATTRIBUTE_NAME);
4318 hiddenEl.removeAttribute('aria-hidden');
4319 }
4320 }
4321 );
4322 }
4323
Ed Tanous904063f2017-03-02 16:48:24 -08004324 $modalStack.close = function(modalInstance, result) {
4325 var modalWindow = openedWindows.get(modalInstance);
Ed Tanous4758d5b2017-06-06 15:28:13 -07004326 unhideBackgroundElements();
Ed Tanous904063f2017-03-02 16:48:24 -08004327 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
4328 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
4329 modalWindow.value.deferred.resolve(result);
4330 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
4331 return true;
4332 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07004333
Ed Tanous904063f2017-03-02 16:48:24 -08004334 return !modalWindow;
4335 };
4336
4337 $modalStack.dismiss = function(modalInstance, reason) {
4338 var modalWindow = openedWindows.get(modalInstance);
Ed Tanous4758d5b2017-06-06 15:28:13 -07004339 unhideBackgroundElements();
Ed Tanous904063f2017-03-02 16:48:24 -08004340 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
4341 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
4342 modalWindow.value.deferred.reject(reason);
4343 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
4344 return true;
4345 }
4346 return !modalWindow;
4347 };
4348
4349 $modalStack.dismissAll = function(reason) {
4350 var topModal = this.getTop();
4351 while (topModal && this.dismiss(topModal.key, reason)) {
4352 topModal = this.getTop();
4353 }
4354 };
4355
4356 $modalStack.getTop = function() {
4357 return openedWindows.top();
4358 };
4359
4360 $modalStack.modalRendered = function(modalInstance) {
4361 var modalWindow = openedWindows.get(modalInstance);
4362 if (modalWindow) {
4363 modalWindow.value.renderDeferred.resolve();
4364 }
4365 };
4366
4367 $modalStack.focusFirstFocusableElement = function(list) {
4368 if (list.length > 0) {
4369 list[0].focus();
4370 return true;
4371 }
4372 return false;
4373 };
4374
4375 $modalStack.focusLastFocusableElement = function(list) {
4376 if (list.length > 0) {
4377 list[list.length - 1].focus();
4378 return true;
4379 }
4380 return false;
4381 };
4382
4383 $modalStack.isModalFocused = function(evt, modalWindow) {
4384 if (evt && modalWindow) {
4385 var modalDomEl = modalWindow.value.modalDomEl;
4386 if (modalDomEl && modalDomEl.length) {
4387 return (evt.target || evt.srcElement) === modalDomEl[0];
4388 }
4389 }
4390 return false;
4391 };
4392
4393 $modalStack.isFocusInFirstItem = function(evt, list) {
4394 if (list.length > 0) {
4395 return (evt.target || evt.srcElement) === list[0];
4396 }
4397 return false;
4398 };
4399
4400 $modalStack.isFocusInLastItem = function(evt, list) {
4401 if (list.length > 0) {
4402 return (evt.target || evt.srcElement) === list[list.length - 1];
4403 }
4404 return false;
4405 };
4406
4407 $modalStack.loadFocusElementList = function(modalWindow) {
4408 if (modalWindow) {
4409 var modalDomE1 = modalWindow.value.modalDomEl;
4410 if (modalDomE1 && modalDomE1.length) {
4411 var elements = modalDomE1[0].querySelectorAll(tabbableSelector);
4412 return elements ?
4413 Array.prototype.filter.call(elements, function(element) {
4414 return isVisible(element);
4415 }) : elements;
4416 }
4417 }
4418 };
4419
4420 return $modalStack;
4421 }])
4422
4423 .provider('$uibModal', function() {
4424 var $modalProvider = {
4425 options: {
4426 animation: true,
4427 backdrop: true, //can also be false or 'static'
4428 keyboard: true
4429 },
4430 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
4431 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
4432 var $modal = {};
4433
4434 function getTemplatePromise(options) {
4435 return options.template ? $q.when(options.template) :
4436 $templateRequest(angular.isFunction(options.templateUrl) ?
4437 options.templateUrl() : options.templateUrl);
4438 }
4439
4440 var promiseChain = null;
4441 $modal.getPromiseChain = function() {
4442 return promiseChain;
4443 };
4444
4445 $modal.open = function(modalOptions) {
4446 var modalResultDeferred = $q.defer();
4447 var modalOpenedDeferred = $q.defer();
4448 var modalClosedDeferred = $q.defer();
4449 var modalRenderDeferred = $q.defer();
4450
4451 //prepare an instance of a modal to be injected into controllers and returned to a caller
4452 var modalInstance = {
4453 result: modalResultDeferred.promise,
4454 opened: modalOpenedDeferred.promise,
4455 closed: modalClosedDeferred.promise,
4456 rendered: modalRenderDeferred.promise,
4457 close: function (result) {
4458 return $modalStack.close(modalInstance, result);
4459 },
4460 dismiss: function (reason) {
4461 return $modalStack.dismiss(modalInstance, reason);
4462 }
4463 };
4464
4465 //merge and clean up options
4466 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
4467 modalOptions.resolve = modalOptions.resolve || {};
4468 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
4469
Ed Tanous4758d5b2017-06-06 15:28:13 -07004470 if (!modalOptions.appendTo.length) {
4471 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
4472 }
4473
Ed Tanous904063f2017-03-02 16:48:24 -08004474 //verify options
4475 if (!modalOptions.component && !modalOptions.template && !modalOptions.templateUrl) {
4476 throw new Error('One of component or template or templateUrl options is required.');
4477 }
4478
4479 var templateAndResolvePromise;
4480 if (modalOptions.component) {
4481 templateAndResolvePromise = $q.when($uibResolve.resolve(modalOptions.resolve, {}, null, null));
4482 } else {
4483 templateAndResolvePromise =
4484 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
4485 }
4486
4487 function resolveWithTemplate() {
4488 return templateAndResolvePromise;
4489 }
4490
4491 // Wait for the resolution of the existing promise chain.
4492 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
4493 // Then add to $modalStack and resolve opened.
4494 // Finally clean up the chain variable if no subsequent modal has overwritten it.
4495 var samePromise;
4496 samePromise = promiseChain = $q.all([promiseChain])
4497 .then(resolveWithTemplate, resolveWithTemplate)
4498 .then(function resolveSuccess(tplAndVars) {
4499 var providedScope = modalOptions.scope || $rootScope;
4500
4501 var modalScope = providedScope.$new();
4502 modalScope.$close = modalInstance.close;
4503 modalScope.$dismiss = modalInstance.dismiss;
4504
4505 modalScope.$on('$destroy', function() {
4506 if (!modalScope.$$uibDestructionScheduled) {
4507 modalScope.$dismiss('$uibUnscheduledDestruction');
4508 }
4509 });
4510
4511 var modal = {
4512 scope: modalScope,
4513 deferred: modalResultDeferred,
4514 renderDeferred: modalRenderDeferred,
4515 closedDeferred: modalClosedDeferred,
4516 animation: modalOptions.animation,
4517 backdrop: modalOptions.backdrop,
4518 keyboard: modalOptions.keyboard,
4519 backdropClass: modalOptions.backdropClass,
4520 windowTopClass: modalOptions.windowTopClass,
4521 windowClass: modalOptions.windowClass,
4522 windowTemplateUrl: modalOptions.windowTemplateUrl,
4523 ariaLabelledBy: modalOptions.ariaLabelledBy,
4524 ariaDescribedBy: modalOptions.ariaDescribedBy,
4525 size: modalOptions.size,
4526 openedClass: modalOptions.openedClass,
4527 appendTo: modalOptions.appendTo
4528 };
4529
4530 var component = {};
4531 var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
4532
4533 if (modalOptions.component) {
4534 constructLocals(component, false, true, false);
4535 component.name = modalOptions.component;
4536 modal.component = component;
4537 } else if (modalOptions.controller) {
4538 constructLocals(ctrlLocals, true, false, true);
4539
4540 // the third param will make the controller instantiate later,private api
4541 // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
4542 ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs);
4543 if (modalOptions.controllerAs && modalOptions.bindToController) {
4544 ctrlInstance = ctrlInstantiate.instance;
4545 ctrlInstance.$close = modalScope.$close;
4546 ctrlInstance.$dismiss = modalScope.$dismiss;
4547 angular.extend(ctrlInstance, {
4548 $resolve: ctrlLocals.$scope.$resolve
4549 }, providedScope);
4550 }
4551
4552 ctrlInstance = ctrlInstantiate();
4553
4554 if (angular.isFunction(ctrlInstance.$onInit)) {
4555 ctrlInstance.$onInit();
4556 }
4557 }
4558
4559 if (!modalOptions.component) {
4560 modal.content = tplAndVars[0];
4561 }
4562
4563 $modalStack.open(modalInstance, modal);
4564 modalOpenedDeferred.resolve(true);
4565
4566 function constructLocals(obj, template, instanceOnScope, injectable) {
4567 obj.$scope = modalScope;
4568 obj.$scope.$resolve = {};
4569 if (instanceOnScope) {
4570 obj.$scope.$uibModalInstance = modalInstance;
4571 } else {
4572 obj.$uibModalInstance = modalInstance;
4573 }
4574
4575 var resolves = template ? tplAndVars[1] : tplAndVars;
4576 angular.forEach(resolves, function(value, key) {
4577 if (injectable) {
4578 obj[key] = value;
4579 }
4580
4581 obj.$scope.$resolve[key] = value;
4582 });
4583 }
4584 }, function resolveError(reason) {
4585 modalOpenedDeferred.reject(reason);
4586 modalResultDeferred.reject(reason);
4587 })['finally'](function() {
4588 if (promiseChain === samePromise) {
4589 promiseChain = null;
4590 }
4591 });
4592
4593 return modalInstance;
4594 };
4595
4596 return $modal;
4597 }
4598 ]
4599 };
4600
4601 return $modalProvider;
4602 });
4603
4604angular.module('ui.bootstrap.paging', [])
4605/**
4606 * Helper internal service for generating common controller code between the
4607 * pager and pagination components
4608 */
4609.factory('uibPaging', ['$parse', function($parse) {
4610 return {
4611 create: function(ctrl, $scope, $attrs) {
4612 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
4613 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
4614 ctrl._watchers = [];
4615
4616 ctrl.init = function(ngModelCtrl, config) {
4617 ctrl.ngModelCtrl = ngModelCtrl;
4618 ctrl.config = config;
4619
4620 ngModelCtrl.$render = function() {
4621 ctrl.render();
4622 };
4623
4624 if ($attrs.itemsPerPage) {
4625 ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
4626 ctrl.itemsPerPage = parseInt(value, 10);
4627 $scope.totalPages = ctrl.calculateTotalPages();
4628 ctrl.updatePage();
4629 }));
4630 } else {
4631 ctrl.itemsPerPage = config.itemsPerPage;
4632 }
4633
4634 $scope.$watch('totalItems', function(newTotal, oldTotal) {
4635 if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
4636 $scope.totalPages = ctrl.calculateTotalPages();
4637 ctrl.updatePage();
4638 }
4639 });
4640 };
4641
4642 ctrl.calculateTotalPages = function() {
4643 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
4644 return Math.max(totalPages || 0, 1);
4645 };
4646
4647 ctrl.render = function() {
4648 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
4649 };
4650
4651 $scope.selectPage = function(page, evt) {
4652 if (evt) {
4653 evt.preventDefault();
4654 }
4655
4656 var clickAllowed = !$scope.ngDisabled || !evt;
4657 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
4658 if (evt && evt.target) {
4659 evt.target.blur();
4660 }
4661 ctrl.ngModelCtrl.$setViewValue(page);
4662 ctrl.ngModelCtrl.$render();
4663 }
4664 };
4665
4666 $scope.getText = function(key) {
4667 return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
4668 };
4669
4670 $scope.noPrevious = function() {
4671 return $scope.page === 1;
4672 };
4673
4674 $scope.noNext = function() {
4675 return $scope.page === $scope.totalPages;
4676 };
4677
4678 ctrl.updatePage = function() {
4679 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
4680
4681 if ($scope.page > $scope.totalPages) {
4682 $scope.selectPage($scope.totalPages);
4683 } else {
4684 ctrl.ngModelCtrl.$render();
4685 }
4686 };
4687
4688 $scope.$on('$destroy', function() {
4689 while (ctrl._watchers.length) {
4690 ctrl._watchers.shift()();
4691 }
4692 });
4693 }
4694 };
4695}]);
4696
4697angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
4698
4699.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
4700 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
4701
4702 uibPaging.create(this, $scope, $attrs);
4703}])
4704
4705.constant('uibPagerConfig', {
4706 itemsPerPage: 10,
4707 previousText: '« Previous',
4708 nextText: 'Next »',
4709 align: true
4710})
4711
4712.directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
4713 return {
4714 scope: {
4715 totalItems: '=',
4716 previousText: '@',
4717 nextText: '@',
4718 ngDisabled: '='
4719 },
4720 require: ['uibPager', '?ngModel'],
4721 restrict: 'A',
4722 controller: 'UibPagerController',
4723 controllerAs: 'pager',
4724 templateUrl: function(element, attrs) {
4725 return attrs.templateUrl || 'uib/template/pager/pager.html';
4726 },
4727 link: function(scope, element, attrs, ctrls) {
4728 element.addClass('pager');
4729 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4730
4731 if (!ngModelCtrl) {
4732 return; // do nothing if no ng-model
4733 }
4734
4735 paginationCtrl.init(ngModelCtrl, uibPagerConfig);
4736 }
4737 };
4738}]);
4739
4740angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging', 'ui.bootstrap.tabindex'])
4741.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
4742 var ctrl = this;
4743 // Setup configuration parameters
4744 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
4745 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
4746 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
4747 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
4748 pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
4749 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
4750 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
Ed Tanous4758d5b2017-06-06 15:28:13 -07004751 $attrs.$set('role', 'menu');
Ed Tanous904063f2017-03-02 16:48:24 -08004752
4753 uibPaging.create(this, $scope, $attrs);
4754
4755 if ($attrs.maxSize) {
4756 ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
4757 maxSize = parseInt(value, 10);
4758 ctrl.render();
4759 }));
4760 }
4761
4762 // Create page object used in template
4763 function makePage(number, text, isActive) {
4764 return {
4765 number: number,
4766 text: text,
4767 active: isActive
4768 };
4769 }
4770
4771 function getPages(currentPage, totalPages) {
4772 var pages = [];
4773
4774 // Default page limits
4775 var startPage = 1, endPage = totalPages;
4776 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
4777
4778 // recompute if maxSize
4779 if (isMaxSized) {
4780 if (rotate) {
4781 // Current page is displayed in the middle of the visible ones
4782 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
4783 endPage = startPage + maxSize - 1;
4784
4785 // Adjust if limit is exceeded
4786 if (endPage > totalPages) {
4787 endPage = totalPages;
4788 startPage = endPage - maxSize + 1;
4789 }
4790 } else {
4791 // Visible pages are paginated with maxSize
4792 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
4793
4794 // Adjust last page if limit is exceeded
4795 endPage = Math.min(startPage + maxSize - 1, totalPages);
4796 }
4797 }
4798
4799 // Add page number links
4800 for (var number = startPage; number <= endPage; number++) {
4801 var page = makePage(number, pageLabel(number), number === currentPage);
4802 pages.push(page);
4803 }
4804
4805 // Add links to move between page sets
4806 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
4807 if (startPage > 1) {
4808 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
4809 var previousPageSet = makePage(startPage - 1, '...', false);
4810 pages.unshift(previousPageSet);
4811 }
4812 if (boundaryLinkNumbers) {
4813 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
4814 var secondPageLink = makePage(2, '2', false);
4815 pages.unshift(secondPageLink);
4816 }
4817 //add the first page
4818 var firstPageLink = makePage(1, '1', false);
4819 pages.unshift(firstPageLink);
4820 }
4821 }
4822
4823 if (endPage < totalPages) {
4824 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
4825 var nextPageSet = makePage(endPage + 1, '...', false);
4826 pages.push(nextPageSet);
4827 }
4828 if (boundaryLinkNumbers) {
4829 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
4830 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
4831 pages.push(secondToLastPageLink);
4832 }
4833 //add the last page
4834 var lastPageLink = makePage(totalPages, totalPages, false);
4835 pages.push(lastPageLink);
4836 }
4837 }
4838 }
4839 return pages;
4840 }
4841
4842 var originalRender = this.render;
4843 this.render = function() {
4844 originalRender();
4845 if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
4846 $scope.pages = getPages($scope.page, $scope.totalPages);
4847 }
4848 };
4849}])
4850
4851.constant('uibPaginationConfig', {
4852 itemsPerPage: 10,
4853 boundaryLinks: false,
4854 boundaryLinkNumbers: false,
4855 directionLinks: true,
4856 firstText: 'First',
4857 previousText: 'Previous',
4858 nextText: 'Next',
4859 lastText: 'Last',
4860 rotate: true,
4861 forceEllipses: false
4862})
4863
4864.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
4865 return {
4866 scope: {
4867 totalItems: '=',
4868 firstText: '@',
4869 previousText: '@',
4870 nextText: '@',
4871 lastText: '@',
4872 ngDisabled:'='
4873 },
4874 require: ['uibPagination', '?ngModel'],
4875 restrict: 'A',
4876 controller: 'UibPaginationController',
4877 controllerAs: 'pagination',
4878 templateUrl: function(element, attrs) {
4879 return attrs.templateUrl || 'uib/template/pagination/pagination.html';
4880 },
4881 link: function(scope, element, attrs, ctrls) {
4882 element.addClass('pagination');
4883 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4884
4885 if (!ngModelCtrl) {
4886 return; // do nothing if no ng-model
4887 }
4888
4889 paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
4890 }
4891 };
4892}]);
4893
4894/**
4895 * The following features are still outstanding: animation as a
4896 * function, placement as a function, inside, support for more triggers than
4897 * just mouse enter/leave, html tooltips, and selector delegation.
4898 */
4899angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
4900
4901/**
4902 * The $tooltip service creates tooltip- and popover-like directives as well as
4903 * houses global options for them.
4904 */
4905.provider('$uibTooltip', function() {
4906 // The default options tooltip and popover.
4907 var defaultOptions = {
4908 placement: 'top',
4909 placementClassPrefix: '',
4910 animation: true,
4911 popupDelay: 0,
4912 popupCloseDelay: 0,
4913 useContentExp: false
4914 };
4915
4916 // Default hide triggers for each show trigger
4917 var triggerMap = {
4918 'mouseenter': 'mouseleave',
4919 'click': 'click',
4920 'outsideClick': 'outsideClick',
4921 'focus': 'blur',
4922 'none': ''
4923 };
4924
4925 // The options specified to the provider globally.
4926 var globalOptions = {};
4927
4928 /**
4929 * `options({})` allows global configuration of all tooltips in the
4930 * application.
4931 *
4932 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
4933 * // place tooltips left instead of top by default
4934 * $tooltipProvider.options( { placement: 'left' } );
4935 * });
4936 */
4937 this.options = function(value) {
4938 angular.extend(globalOptions, value);
4939 };
4940
4941 /**
4942 * This allows you to extend the set of trigger mappings available. E.g.:
4943 *
4944 * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
4945 */
4946 this.setTriggers = function setTriggers(triggers) {
4947 angular.extend(triggerMap, triggers);
4948 };
4949
4950 /**
4951 * This is a helper function for translating camel-case to snake_case.
4952 */
4953 function snake_case(name) {
4954 var regexp = /[A-Z]/g;
4955 var separator = '-';
4956 return name.replace(regexp, function(letter, pos) {
4957 return (pos ? separator : '') + letter.toLowerCase();
4958 });
4959 }
4960
4961 /**
4962 * Returns the actual instance of the $tooltip service.
4963 * TODO support multiple triggers
4964 */
4965 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
4966 var openedTooltips = $$stackedMap.createNew();
4967 $document.on('keyup', keypressListener);
4968
4969 $rootScope.$on('$destroy', function() {
4970 $document.off('keyup', keypressListener);
4971 });
4972
4973 function keypressListener(e) {
4974 if (e.which === 27) {
4975 var last = openedTooltips.top();
4976 if (last) {
4977 last.value.close();
4978 last = null;
4979 }
4980 }
4981 }
4982
4983 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
4984 options = angular.extend({}, defaultOptions, globalOptions, options);
4985
4986 /**
4987 * Returns an object of show and hide triggers.
4988 *
4989 * If a trigger is supplied,
4990 * it is used to show the tooltip; otherwise, it will use the `trigger`
4991 * option passed to the `$tooltipProvider.options` method; else it will
4992 * default to the trigger supplied to this directive factory.
4993 *
4994 * The hide trigger is based on the show trigger. If the `trigger` option
4995 * was passed to the `$tooltipProvider.options` method, it will use the
4996 * mapped trigger from `triggerMap` or the passed trigger if the map is
4997 * undefined; otherwise, it uses the `triggerMap` value of the show
4998 * trigger; else it will just use the show trigger.
4999 */
5000 function getTriggers(trigger) {
5001 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
5002 var hide = show.map(function(trigger) {
5003 return triggerMap[trigger] || trigger;
5004 });
5005 return {
5006 show: show,
5007 hide: hide
5008 };
5009 }
5010
5011 var directiveName = snake_case(ttType);
5012
5013 var startSym = $interpolate.startSymbol();
5014 var endSym = $interpolate.endSymbol();
5015 var template =
5016 '<div '+ directiveName + '-popup ' +
5017 'uib-title="' + startSym + 'title' + endSym + '" ' +
5018 (options.useContentExp ?
5019 'content-exp="contentExp()" ' :
5020 'content="' + startSym + 'content' + endSym + '" ') +
5021 'origin-scope="origScope" ' +
5022 'class="uib-position-measure ' + prefix + '" ' +
5023 'tooltip-animation-class="fade"' +
5024 'uib-tooltip-classes ' +
5025 'ng-class="{ in: isOpen }" ' +
5026 '>' +
5027 '</div>';
5028
5029 return {
5030 compile: function(tElem, tAttrs) {
5031 var tooltipLinker = $compile(template);
5032
5033 return function link(scope, element, attrs, tooltipCtrl) {
5034 var tooltip;
5035 var tooltipLinkedScope;
5036 var transitionTimeout;
5037 var showTimeout;
5038 var hideTimeout;
5039 var positionTimeout;
Ed Tanous4758d5b2017-06-06 15:28:13 -07005040 var adjustmentTimeout;
Ed Tanous904063f2017-03-02 16:48:24 -08005041 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
5042 var triggers = getTriggers(undefined);
5043 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
5044 var ttScope = scope.$new(true);
5045 var repositionScheduled = false;
5046 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
5047 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
5048 var observers = [];
5049 var lastPlacement;
5050
5051 var positionTooltip = function() {
5052 // check if tooltip exists and is not empty
5053 if (!tooltip || !tooltip.html()) { return; }
5054
5055 if (!positionTimeout) {
5056 positionTimeout = $timeout(function() {
5057 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
5058 var initialHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
5059 var elementPos = appendToBody ? $position.offset(element) : $position.position(element);
5060 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
5061 var placementClasses = ttPosition.placement.split('-');
5062
5063 if (!tooltip.hasClass(placementClasses[0])) {
5064 tooltip.removeClass(lastPlacement.split('-')[0]);
5065 tooltip.addClass(placementClasses[0]);
5066 }
5067
5068 if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
5069 tooltip.removeClass(options.placementClassPrefix + lastPlacement);
5070 tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
5071 }
5072
Ed Tanous4758d5b2017-06-06 15:28:13 -07005073 adjustmentTimeout = $timeout(function() {
Ed Tanous904063f2017-03-02 16:48:24 -08005074 var currentHeight = angular.isDefined(tooltip.offsetHeight) ? tooltip.offsetHeight : tooltip.prop('offsetHeight');
5075 var adjustment = $position.adjustTop(placementClasses, elementPos, initialHeight, currentHeight);
5076 if (adjustment) {
5077 tooltip.css(adjustment);
5078 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07005079 adjustmentTimeout = null;
Ed Tanous904063f2017-03-02 16:48:24 -08005080 }, 0, false);
5081
5082 // first time through tt element will have the
5083 // uib-position-measure class or if the placement
5084 // has changed we need to position the arrow.
5085 if (tooltip.hasClass('uib-position-measure')) {
5086 $position.positionArrow(tooltip, ttPosition.placement);
5087 tooltip.removeClass('uib-position-measure');
5088 } else if (lastPlacement !== ttPosition.placement) {
5089 $position.positionArrow(tooltip, ttPosition.placement);
5090 }
5091 lastPlacement = ttPosition.placement;
5092
5093 positionTimeout = null;
5094 }, 0, false);
5095 }
5096 };
5097
5098 // Set up the correct scope to allow transclusion later
5099 ttScope.origScope = scope;
5100
5101 // By default, the tooltip is not open.
5102 // TODO add ability to start tooltip opened
5103 ttScope.isOpen = false;
5104
5105 function toggleTooltipBind() {
5106 if (!ttScope.isOpen) {
5107 showTooltipBind();
5108 } else {
5109 hideTooltipBind();
5110 }
5111 }
5112
5113 // Show the tooltip with delay if specified, otherwise show it immediately
5114 function showTooltipBind() {
5115 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
5116 return;
5117 }
5118
5119 cancelHide();
5120 prepareTooltip();
5121
5122 if (ttScope.popupDelay) {
5123 // Do nothing if the tooltip was already scheduled to pop-up.
5124 // This happens if show is triggered multiple times before any hide is triggered.
5125 if (!showTimeout) {
5126 showTimeout = $timeout(show, ttScope.popupDelay, false);
5127 }
5128 } else {
5129 show();
5130 }
5131 }
5132
5133 function hideTooltipBind() {
5134 cancelShow();
5135
5136 if (ttScope.popupCloseDelay) {
5137 if (!hideTimeout) {
5138 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
5139 }
5140 } else {
5141 hide();
5142 }
5143 }
5144
5145 // Show the tooltip popup element.
5146 function show() {
5147 cancelShow();
5148 cancelHide();
5149
5150 // Don't show empty tooltips.
5151 if (!ttScope.content) {
5152 return angular.noop;
5153 }
5154
5155 createTooltip();
5156
5157 // And show the tooltip.
5158 ttScope.$evalAsync(function() {
5159 ttScope.isOpen = true;
5160 assignIsOpen(true);
5161 positionTooltip();
5162 });
5163 }
5164
5165 function cancelShow() {
5166 if (showTimeout) {
5167 $timeout.cancel(showTimeout);
5168 showTimeout = null;
5169 }
5170
5171 if (positionTimeout) {
5172 $timeout.cancel(positionTimeout);
5173 positionTimeout = null;
5174 }
5175 }
5176
5177 // Hide the tooltip popup element.
5178 function hide() {
5179 if (!ttScope) {
5180 return;
5181 }
5182
5183 // First things first: we don't show it anymore.
5184 ttScope.$evalAsync(function() {
5185 if (ttScope) {
5186 ttScope.isOpen = false;
5187 assignIsOpen(false);
5188 // And now we remove it from the DOM. However, if we have animation, we
5189 // need to wait for it to expire beforehand.
5190 // FIXME: this is a placeholder for a port of the transitions library.
5191 // The fade transition in TWBS is 150ms.
5192 if (ttScope.animation) {
5193 if (!transitionTimeout) {
5194 transitionTimeout = $timeout(removeTooltip, 150, false);
5195 }
5196 } else {
5197 removeTooltip();
5198 }
5199 }
5200 });
5201 }
5202
5203 function cancelHide() {
5204 if (hideTimeout) {
5205 $timeout.cancel(hideTimeout);
5206 hideTimeout = null;
5207 }
5208
5209 if (transitionTimeout) {
5210 $timeout.cancel(transitionTimeout);
5211 transitionTimeout = null;
5212 }
5213 }
5214
5215 function createTooltip() {
5216 // There can only be one tooltip element per directive shown at once.
5217 if (tooltip) {
5218 return;
5219 }
5220
5221 tooltipLinkedScope = ttScope.$new();
5222 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
5223 if (appendToBody) {
5224 $document.find('body').append(tooltip);
5225 } else {
5226 element.after(tooltip);
5227 }
5228 });
5229
5230 openedTooltips.add(ttScope, {
5231 close: hide
5232 });
5233
5234 prepObservers();
5235 }
5236
5237 function removeTooltip() {
5238 cancelShow();
5239 cancelHide();
5240 unregisterObservers();
5241
5242 if (tooltip) {
5243 tooltip.remove();
Ed Tanous4758d5b2017-06-06 15:28:13 -07005244
Ed Tanous904063f2017-03-02 16:48:24 -08005245 tooltip = null;
Ed Tanous4758d5b2017-06-06 15:28:13 -07005246 if (adjustmentTimeout) {
5247 $timeout.cancel(adjustmentTimeout);
5248 }
Ed Tanous904063f2017-03-02 16:48:24 -08005249 }
5250
5251 openedTooltips.remove(ttScope);
5252
5253 if (tooltipLinkedScope) {
5254 tooltipLinkedScope.$destroy();
5255 tooltipLinkedScope = null;
5256 }
5257 }
5258
5259 /**
5260 * Set the initial scope values. Once
5261 * the tooltip is created, the observers
5262 * will be added to keep things in sync.
5263 */
5264 function prepareTooltip() {
5265 ttScope.title = attrs[prefix + 'Title'];
5266 if (contentParse) {
5267 ttScope.content = contentParse(scope);
5268 } else {
5269 ttScope.content = attrs[ttType];
5270 }
5271
5272 ttScope.popupClass = attrs[prefix + 'Class'];
5273 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
5274 var placement = $position.parsePlacement(ttScope.placement);
5275 lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
5276
5277 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
5278 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
5279 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
5280 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
5281 }
5282
5283 function assignIsOpen(isOpen) {
5284 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
5285 isOpenParse.assign(scope, isOpen);
5286 }
5287 }
5288
5289 ttScope.contentExp = function() {
5290 return ttScope.content;
5291 };
5292
5293 /**
5294 * Observe the relevant attributes.
5295 */
5296 attrs.$observe('disabled', function(val) {
5297 if (val) {
5298 cancelShow();
5299 }
5300
5301 if (val && ttScope.isOpen) {
5302 hide();
5303 }
5304 });
5305
5306 if (isOpenParse) {
5307 scope.$watch(isOpenParse, function(val) {
5308 if (ttScope && !val === ttScope.isOpen) {
5309 toggleTooltipBind();
5310 }
5311 });
5312 }
5313
5314 function prepObservers() {
5315 observers.length = 0;
5316
5317 if (contentParse) {
5318 observers.push(
5319 scope.$watch(contentParse, function(val) {
5320 ttScope.content = val;
5321 if (!val && ttScope.isOpen) {
5322 hide();
5323 }
5324 })
5325 );
5326
5327 observers.push(
5328 tooltipLinkedScope.$watch(function() {
5329 if (!repositionScheduled) {
5330 repositionScheduled = true;
5331 tooltipLinkedScope.$$postDigest(function() {
5332 repositionScheduled = false;
5333 if (ttScope && ttScope.isOpen) {
5334 positionTooltip();
5335 }
5336 });
5337 }
5338 })
5339 );
5340 } else {
5341 observers.push(
5342 attrs.$observe(ttType, function(val) {
5343 ttScope.content = val;
5344 if (!val && ttScope.isOpen) {
5345 hide();
5346 } else {
5347 positionTooltip();
5348 }
5349 })
5350 );
5351 }
5352
5353 observers.push(
5354 attrs.$observe(prefix + 'Title', function(val) {
5355 ttScope.title = val;
5356 if (ttScope.isOpen) {
5357 positionTooltip();
5358 }
5359 })
5360 );
5361
5362 observers.push(
5363 attrs.$observe(prefix + 'Placement', function(val) {
5364 ttScope.placement = val ? val : options.placement;
5365 if (ttScope.isOpen) {
5366 positionTooltip();
5367 }
5368 })
5369 );
5370 }
5371
5372 function unregisterObservers() {
5373 if (observers.length) {
5374 angular.forEach(observers, function(observer) {
5375 observer();
5376 });
5377 observers.length = 0;
5378 }
5379 }
5380
5381 // hide tooltips/popovers for outsideClick trigger
5382 function bodyHideTooltipBind(e) {
5383 if (!ttScope || !ttScope.isOpen || !tooltip) {
5384 return;
5385 }
5386 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
5387 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
5388 hideTooltipBind();
5389 }
5390 }
5391
Ed Tanous4758d5b2017-06-06 15:28:13 -07005392 // KeyboardEvent handler to hide the tooltip on Escape key press
5393 function hideOnEscapeKey(e) {
5394 if (e.which === 27) {
5395 hideTooltipBind();
5396 }
5397 }
5398
Ed Tanous904063f2017-03-02 16:48:24 -08005399 var unregisterTriggers = function() {
5400 triggers.show.forEach(function(trigger) {
5401 if (trigger === 'outsideClick') {
5402 element.off('click', toggleTooltipBind);
5403 } else {
5404 element.off(trigger, showTooltipBind);
5405 element.off(trigger, toggleTooltipBind);
5406 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07005407 element.off('keypress', hideOnEscapeKey);
Ed Tanous904063f2017-03-02 16:48:24 -08005408 });
5409 triggers.hide.forEach(function(trigger) {
5410 if (trigger === 'outsideClick') {
5411 $document.off('click', bodyHideTooltipBind);
5412 } else {
5413 element.off(trigger, hideTooltipBind);
5414 }
5415 });
5416 };
5417
5418 function prepTriggers() {
5419 var showTriggers = [], hideTriggers = [];
5420 var val = scope.$eval(attrs[prefix + 'Trigger']);
5421 unregisterTriggers();
5422
5423 if (angular.isObject(val)) {
5424 Object.keys(val).forEach(function(key) {
5425 showTriggers.push(key);
5426 hideTriggers.push(val[key]);
5427 });
5428 triggers = {
5429 show: showTriggers,
5430 hide: hideTriggers
5431 };
5432 } else {
5433 triggers = getTriggers(val);
5434 }
5435
5436 if (triggers.show !== 'none') {
5437 triggers.show.forEach(function(trigger, idx) {
5438 if (trigger === 'outsideClick') {
5439 element.on('click', toggleTooltipBind);
5440 $document.on('click', bodyHideTooltipBind);
5441 } else if (trigger === triggers.hide[idx]) {
5442 element.on(trigger, toggleTooltipBind);
5443 } else if (trigger) {
5444 element.on(trigger, showTooltipBind);
5445 element.on(triggers.hide[idx], hideTooltipBind);
5446 }
Ed Tanous4758d5b2017-06-06 15:28:13 -07005447 element.on('keypress', hideOnEscapeKey);
Ed Tanous904063f2017-03-02 16:48:24 -08005448 });
5449 }
5450 }
5451
5452 prepTriggers();
5453
5454 var animation = scope.$eval(attrs[prefix + 'Animation']);
5455 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
5456
5457 var appendToBodyVal;
5458 var appendKey = prefix + 'AppendToBody';
5459 if (appendKey in attrs && attrs[appendKey] === undefined) {
5460 appendToBodyVal = true;
5461 } else {
5462 appendToBodyVal = scope.$eval(attrs[appendKey]);
5463 }
5464
5465 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
5466
5467 // Make sure tooltip is destroyed and removed.
5468 scope.$on('$destroy', function onDestroyTooltip() {
5469 unregisterTriggers();
5470 removeTooltip();
5471 ttScope = null;
5472 });
5473 };
5474 }
5475 };
5476 };
5477 }];
5478})
5479
5480// This is mostly ngInclude code but with a custom scope
5481.directive('uibTooltipTemplateTransclude', [
5482 '$animate', '$sce', '$compile', '$templateRequest',
5483function ($animate, $sce, $compile, $templateRequest) {
5484 return {
5485 link: function(scope, elem, attrs) {
5486 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
5487
5488 var changeCounter = 0,
5489 currentScope,
5490 previousElement,
5491 currentElement;
5492
5493 var cleanupLastIncludeContent = function() {
5494 if (previousElement) {
5495 previousElement.remove();
5496 previousElement = null;
5497 }
5498
5499 if (currentScope) {
5500 currentScope.$destroy();
5501 currentScope = null;
5502 }
5503
5504 if (currentElement) {
5505 $animate.leave(currentElement).then(function() {
5506 previousElement = null;
5507 });
5508 previousElement = currentElement;
5509 currentElement = null;
5510 }
5511 };
5512
5513 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
5514 var thisChangeId = ++changeCounter;
5515
5516 if (src) {
5517 //set the 2nd param to true to ignore the template request error so that the inner
5518 //contents and scope can be cleaned up.
5519 $templateRequest(src, true).then(function(response) {
5520 if (thisChangeId !== changeCounter) { return; }
5521 var newScope = origScope.$new();
5522 var template = response;
5523
5524 var clone = $compile(template)(newScope, function(clone) {
5525 cleanupLastIncludeContent();
5526 $animate.enter(clone, elem);
5527 });
5528
5529 currentScope = newScope;
5530 currentElement = clone;
5531
5532 currentScope.$emit('$includeContentLoaded', src);
5533 }, function() {
5534 if (thisChangeId === changeCounter) {
5535 cleanupLastIncludeContent();
5536 scope.$emit('$includeContentError', src);
5537 }
5538 });
5539 scope.$emit('$includeContentRequested', src);
5540 } else {
5541 cleanupLastIncludeContent();
5542 }
5543 });
5544
5545 scope.$on('$destroy', cleanupLastIncludeContent);
5546 }
5547 };
5548}])
5549
5550/**
5551 * Note that it's intentional that these classes are *not* applied through $animate.
5552 * They must not be animated as they're expected to be present on the tooltip on
5553 * initialization.
5554 */
5555.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
5556 return {
5557 restrict: 'A',
5558 link: function(scope, element, attrs) {
5559 // need to set the primary position so the
5560 // arrow has space during position measure.
5561 // tooltip.positionTooltip()
5562 if (scope.placement) {
5563 // // There are no top-left etc... classes
5564 // // in TWBS, so we need the primary position.
5565 var position = $uibPosition.parsePlacement(scope.placement);
5566 element.addClass(position[0]);
5567 }
5568
5569 if (scope.popupClass) {
5570 element.addClass(scope.popupClass);
5571 }
5572
5573 if (scope.animation) {
5574 element.addClass(attrs.tooltipAnimationClass);
5575 }
5576 }
5577 };
5578}])
5579
5580.directive('uibTooltipPopup', function() {
5581 return {
5582 restrict: 'A',
5583 scope: { content: '@' },
5584 templateUrl: 'uib/template/tooltip/tooltip-popup.html'
5585 };
5586})
5587
5588.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
5589 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
5590}])
5591
5592.directive('uibTooltipTemplatePopup', function() {
5593 return {
5594 restrict: 'A',
5595 scope: { contentExp: '&', originScope: '&' },
5596 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
5597 };
5598})
5599
5600.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
5601 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
5602 useContentExp: true
5603 });
5604}])
5605
5606.directive('uibTooltipHtmlPopup', function() {
5607 return {
5608 restrict: 'A',
5609 scope: { contentExp: '&' },
5610 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
5611 };
5612})
5613
5614.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
5615 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
5616 useContentExp: true
5617 });
5618}]);
5619
5620/**
5621 * The following features are still outstanding: popup delay, animation as a
5622 * function, placement as a function, inside, support for more triggers than
5623 * just mouse enter/leave, and selector delegatation.
5624 */
5625angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
5626
5627.directive('uibPopoverTemplatePopup', function() {
5628 return {
5629 restrict: 'A',
5630 scope: { uibTitle: '@', contentExp: '&', originScope: '&' },
5631 templateUrl: 'uib/template/popover/popover-template.html'
5632 };
5633})
5634
5635.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
5636 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
5637 useContentExp: true
5638 });
5639}])
5640
5641.directive('uibPopoverHtmlPopup', function() {
5642 return {
5643 restrict: 'A',
5644 scope: { contentExp: '&', uibTitle: '@' },
5645 templateUrl: 'uib/template/popover/popover-html.html'
5646 };
5647})
5648
5649.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
5650 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
5651 useContentExp: true
5652 });
5653}])
5654
5655.directive('uibPopoverPopup', function() {
5656 return {
5657 restrict: 'A',
5658 scope: { uibTitle: '@', content: '@' },
5659 templateUrl: 'uib/template/popover/popover.html'
5660 };
5661})
5662
5663.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
5664 return $uibTooltip('uibPopover', 'popover', 'click');
5665}]);
5666
5667angular.module('ui.bootstrap.progressbar', [])
5668
5669.constant('uibProgressConfig', {
5670 animate: true,
5671 max: 100
5672})
5673
5674.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
5675 var self = this,
5676 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
5677
5678 this.bars = [];
5679 $scope.max = getMaxOrDefault();
5680
5681 this.addBar = function(bar, element, attrs) {
5682 if (!animate) {
5683 element.css({'transition': 'none'});
5684 }
5685
5686 this.bars.push(bar);
5687
5688 bar.max = getMaxOrDefault();
5689 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
5690
5691 bar.$watch('value', function(value) {
5692 bar.recalculatePercentage();
5693 });
5694
5695 bar.recalculatePercentage = function() {
5696 var totalPercentage = self.bars.reduce(function(total, bar) {
5697 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
5698 return total + bar.percent;
5699 }, 0);
5700
5701 if (totalPercentage > 100) {
5702 bar.percent -= totalPercentage - 100;
5703 }
5704 };
5705
5706 bar.$on('$destroy', function() {
5707 element = null;
5708 self.removeBar(bar);
5709 });
5710 };
5711
5712 this.removeBar = function(bar) {
5713 this.bars.splice(this.bars.indexOf(bar), 1);
5714 this.bars.forEach(function (bar) {
5715 bar.recalculatePercentage();
5716 });
5717 };
5718
5719 //$attrs.$observe('maxParam', function(maxParam) {
5720 $scope.$watch('maxParam', function(maxParam) {
5721 self.bars.forEach(function(bar) {
5722 bar.max = getMaxOrDefault();
5723 bar.recalculatePercentage();
5724 });
5725 });
5726
5727 function getMaxOrDefault () {
5728 return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
5729 }
5730}])
5731
5732.directive('uibProgress', function() {
5733 return {
5734 replace: true,
5735 transclude: true,
5736 controller: 'UibProgressController',
5737 require: 'uibProgress',
5738 scope: {
5739 maxParam: '=?max'
5740 },
5741 templateUrl: 'uib/template/progressbar/progress.html'
5742 };
5743})
5744
5745.directive('uibBar', function() {
5746 return {
5747 replace: true,
5748 transclude: true,
5749 require: '^uibProgress',
5750 scope: {
5751 value: '=',
5752 type: '@'
5753 },
5754 templateUrl: 'uib/template/progressbar/bar.html',
5755 link: function(scope, element, attrs, progressCtrl) {
5756 progressCtrl.addBar(scope, element, attrs);
5757 }
5758 };
5759})
5760
5761.directive('uibProgressbar', function() {
5762 return {
5763 replace: true,
5764 transclude: true,
5765 controller: 'UibProgressController',
5766 scope: {
5767 value: '=',
5768 maxParam: '=?max',
5769 type: '@'
5770 },
5771 templateUrl: 'uib/template/progressbar/progressbar.html',
5772 link: function(scope, element, attrs, progressCtrl) {
5773 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
5774 }
5775 };
5776});
5777
5778angular.module('ui.bootstrap.rating', [])
5779
5780.constant('uibRatingConfig', {
5781 max: 5,
5782 stateOn: null,
5783 stateOff: null,
5784 enableReset: true,
5785 titles: ['one', 'two', 'three', 'four', 'five']
5786})
5787
5788.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
5789 var ngModelCtrl = { $setViewValue: angular.noop },
5790 self = this;
5791
5792 this.init = function(ngModelCtrl_) {
5793 ngModelCtrl = ngModelCtrl_;
5794 ngModelCtrl.$render = this.render;
5795
5796 ngModelCtrl.$formatters.push(function(value) {
5797 if (angular.isNumber(value) && value << 0 !== value) {
5798 value = Math.round(value);
5799 }
5800
5801 return value;
5802 });
5803
5804 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
5805 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
5806 this.enableReset = angular.isDefined($attrs.enableReset) ?
5807 $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
5808 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
5809 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
5810 tmpTitles : ratingConfig.titles;
5811
5812 var ratingStates = angular.isDefined($attrs.ratingStates) ?
5813 $scope.$parent.$eval($attrs.ratingStates) :
5814 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
5815 $scope.range = this.buildTemplateObjects(ratingStates);
5816 };
5817
5818 this.buildTemplateObjects = function(states) {
5819 for (var i = 0, n = states.length; i < n; i++) {
5820 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
5821 }
5822 return states;
5823 };
5824
5825 this.getTitle = function(index) {
5826 if (index >= this.titles.length) {
5827 return index + 1;
5828 }
5829
5830 return this.titles[index];
5831 };
5832
5833 $scope.rate = function(value) {
5834 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
5835 var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
5836 ngModelCtrl.$setViewValue(newViewValue);
5837 ngModelCtrl.$render();
5838 }
5839 };
5840
5841 $scope.enter = function(value) {
5842 if (!$scope.readonly) {
5843 $scope.value = value;
5844 }
5845 $scope.onHover({value: value});
5846 };
5847
5848 $scope.reset = function() {
5849 $scope.value = ngModelCtrl.$viewValue;
5850 $scope.onLeave();
5851 };
5852
5853 $scope.onKeydown = function(evt) {
5854 if (/(37|38|39|40)/.test(evt.which)) {
5855 evt.preventDefault();
5856 evt.stopPropagation();
5857 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
5858 }
5859 };
5860
5861 this.render = function() {
5862 $scope.value = ngModelCtrl.$viewValue;
5863 $scope.title = self.getTitle($scope.value - 1);
5864 };
5865}])
5866
5867.directive('uibRating', function() {
5868 return {
5869 require: ['uibRating', 'ngModel'],
5870 restrict: 'A',
5871 scope: {
5872 readonly: '=?readOnly',
5873 onHover: '&',
5874 onLeave: '&'
5875 },
5876 controller: 'UibRatingController',
5877 templateUrl: 'uib/template/rating/rating.html',
5878 link: function(scope, element, attrs, ctrls) {
5879 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
5880 ratingCtrl.init(ngModelCtrl);
5881 }
5882 };
5883});
5884
5885angular.module('ui.bootstrap.tabs', [])
5886
5887.controller('UibTabsetController', ['$scope', function ($scope) {
5888 var ctrl = this,
5889 oldIndex;
5890 ctrl.tabs = [];
5891
5892 ctrl.select = function(index, evt) {
5893 if (!destroyed) {
5894 var previousIndex = findTabIndex(oldIndex);
5895 var previousSelected = ctrl.tabs[previousIndex];
5896 if (previousSelected) {
5897 previousSelected.tab.onDeselect({
5898 $event: evt,
5899 $selectedIndex: index
5900 });
5901 if (evt && evt.isDefaultPrevented()) {
5902 return;
5903 }
5904 previousSelected.tab.active = false;
5905 }
5906
5907 var selected = ctrl.tabs[index];
5908 if (selected) {
5909 selected.tab.onSelect({
5910 $event: evt
5911 });
5912 selected.tab.active = true;
5913 ctrl.active = selected.index;
5914 oldIndex = selected.index;
5915 } else if (!selected && angular.isDefined(oldIndex)) {
5916 ctrl.active = null;
5917 oldIndex = null;
5918 }
5919 }
5920 };
5921
5922 ctrl.addTab = function addTab(tab) {
5923 ctrl.tabs.push({
5924 tab: tab,
5925 index: tab.index
5926 });
5927 ctrl.tabs.sort(function(t1, t2) {
5928 if (t1.index > t2.index) {
5929 return 1;
5930 }
5931
5932 if (t1.index < t2.index) {
5933 return -1;
5934 }
5935
5936 return 0;
5937 });
5938
5939 if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) {
5940 var newActiveIndex = findTabIndex(tab.index);
5941 ctrl.select(newActiveIndex);
5942 }
5943 };
5944
5945 ctrl.removeTab = function removeTab(tab) {
5946 var index;
5947 for (var i = 0; i < ctrl.tabs.length; i++) {
5948 if (ctrl.tabs[i].tab === tab) {
5949 index = i;
5950 break;
5951 }
5952 }
5953
5954 if (ctrl.tabs[index].index === ctrl.active) {
5955 var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
5956 index - 1 : index + 1 % ctrl.tabs.length;
5957 ctrl.select(newActiveTabIndex);
5958 }
5959
5960 ctrl.tabs.splice(index, 1);
5961 };
5962
5963 $scope.$watch('tabset.active', function(val) {
5964 if (angular.isDefined(val) && val !== oldIndex) {
5965 ctrl.select(findTabIndex(val));
5966 }
5967 });
5968
5969 var destroyed;
5970 $scope.$on('$destroy', function() {
5971 destroyed = true;
5972 });
5973
5974 function findTabIndex(index) {
5975 for (var i = 0; i < ctrl.tabs.length; i++) {
5976 if (ctrl.tabs[i].index === index) {
5977 return i;
5978 }
5979 }
5980 }
5981}])
5982
5983.directive('uibTabset', function() {
5984 return {
5985 transclude: true,
5986 replace: true,
5987 scope: {},
5988 bindToController: {
5989 active: '=?',
5990 type: '@'
5991 },
5992 controller: 'UibTabsetController',
5993 controllerAs: 'tabset',
5994 templateUrl: function(element, attrs) {
5995 return attrs.templateUrl || 'uib/template/tabs/tabset.html';
5996 },
5997 link: function(scope, element, attrs) {
5998 scope.vertical = angular.isDefined(attrs.vertical) ?
5999 scope.$parent.$eval(attrs.vertical) : false;
6000 scope.justified = angular.isDefined(attrs.justified) ?
6001 scope.$parent.$eval(attrs.justified) : false;
6002 }
6003 };
6004})
6005
6006.directive('uibTab', ['$parse', function($parse) {
6007 return {
6008 require: '^uibTabset',
6009 replace: true,
6010 templateUrl: function(element, attrs) {
6011 return attrs.templateUrl || 'uib/template/tabs/tab.html';
6012 },
6013 transclude: true,
6014 scope: {
6015 heading: '@',
6016 index: '=?',
6017 classes: '@?',
6018 onSelect: '&select', //This callback is called in contentHeadingTransclude
6019 //once it inserts the tab's content into the dom
6020 onDeselect: '&deselect'
6021 },
6022 controller: function() {
6023 //Empty controller so other directives can require being 'under' a tab
6024 },
6025 controllerAs: 'tab',
6026 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
6027 scope.disabled = false;
6028 if (attrs.disable) {
6029 scope.$parent.$watch($parse(attrs.disable), function(value) {
6030 scope.disabled = !! value;
6031 });
6032 }
6033
6034 if (angular.isUndefined(attrs.index)) {
6035 if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
6036 scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
6037 } else {
6038 scope.index = 0;
6039 }
6040 }
6041
6042 if (angular.isUndefined(attrs.classes)) {
6043 scope.classes = '';
6044 }
6045
6046 scope.select = function(evt) {
6047 if (!scope.disabled) {
6048 var index;
6049 for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
6050 if (tabsetCtrl.tabs[i].tab === scope) {
6051 index = i;
6052 break;
6053 }
6054 }
6055
6056 tabsetCtrl.select(index, evt);
6057 }
6058 };
6059
6060 tabsetCtrl.addTab(scope);
6061 scope.$on('$destroy', function() {
6062 tabsetCtrl.removeTab(scope);
6063 });
6064
6065 //We need to transclude later, once the content container is ready.
6066 //when this link happens, we're inside a tab heading.
6067 scope.$transcludeFn = transclude;
6068 }
6069 };
6070}])
6071
6072.directive('uibTabHeadingTransclude', function() {
6073 return {
6074 restrict: 'A',
6075 require: '^uibTab',
6076 link: function(scope, elm) {
6077 scope.$watch('headingElement', function updateHeadingElement(heading) {
6078 if (heading) {
6079 elm.html('');
6080 elm.append(heading);
6081 }
6082 });
6083 }
6084 };
6085})
6086
6087.directive('uibTabContentTransclude', function() {
6088 return {
6089 restrict: 'A',
6090 require: '^uibTabset',
6091 link: function(scope, elm, attrs) {
6092 var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
6093
6094 //Now our tab is ready to be transcluded: both the tab heading area
6095 //and the tab content area are loaded. Transclude 'em both.
6096 tab.$transcludeFn(tab.$parent, function(contents) {
6097 angular.forEach(contents, function(node) {
6098 if (isTabHeading(node)) {
6099 //Let tabHeadingTransclude know.
6100 tab.headingElement = node;
6101 } else {
6102 elm.append(node);
6103 }
6104 });
6105 });
6106 }
6107 };
6108
6109 function isTabHeading(node) {
6110 return node.tagName && (
6111 node.hasAttribute('uib-tab-heading') ||
6112 node.hasAttribute('data-uib-tab-heading') ||
6113 node.hasAttribute('x-uib-tab-heading') ||
6114 node.tagName.toLowerCase() === 'uib-tab-heading' ||
6115 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
6116 node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
6117 node.tagName.toLowerCase() === 'uib:tab-heading'
6118 );
6119 }
6120});
6121
6122angular.module('ui.bootstrap.timepicker', [])
6123
6124.constant('uibTimepickerConfig', {
6125 hourStep: 1,
6126 minuteStep: 1,
6127 secondStep: 1,
6128 showMeridian: true,
6129 showSeconds: false,
6130 meridians: null,
6131 readonlyInput: false,
6132 mousewheel: true,
6133 arrowkeys: true,
6134 showSpinners: true,
6135 templateUrl: 'uib/template/timepicker/timepicker.html'
6136})
6137
6138.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006139 var hoursModelCtrl, minutesModelCtrl, secondsModelCtrl;
Ed Tanous904063f2017-03-02 16:48:24 -08006140 var selected = new Date(),
6141 watchers = [],
6142 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
6143 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
6144 padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;
6145
6146 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
6147 $element.removeAttr('tabindex');
6148
6149 this.init = function(ngModelCtrl_, inputs) {
6150 ngModelCtrl = ngModelCtrl_;
6151 ngModelCtrl.$render = this.render;
6152
6153 ngModelCtrl.$formatters.unshift(function(modelValue) {
6154 return modelValue ? new Date(modelValue) : null;
6155 });
6156
6157 var hoursInputEl = inputs.eq(0),
6158 minutesInputEl = inputs.eq(1),
6159 secondsInputEl = inputs.eq(2);
6160
Ed Tanous4758d5b2017-06-06 15:28:13 -07006161 hoursModelCtrl = hoursInputEl.controller('ngModel');
6162 minutesModelCtrl = minutesInputEl.controller('ngModel');
6163 secondsModelCtrl = secondsInputEl.controller('ngModel');
6164
Ed Tanous904063f2017-03-02 16:48:24 -08006165 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
6166
6167 if (mousewheel) {
6168 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6169 }
6170
6171 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
6172 if (arrowkeys) {
6173 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6174 }
6175
6176 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
6177 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
6178 };
6179
6180 var hourStep = timepickerConfig.hourStep;
6181 if ($attrs.hourStep) {
6182 watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
6183 hourStep = +value;
6184 }));
6185 }
6186
6187 var minuteStep = timepickerConfig.minuteStep;
6188 if ($attrs.minuteStep) {
6189 watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
6190 minuteStep = +value;
6191 }));
6192 }
6193
6194 var min;
6195 watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
6196 var dt = new Date(value);
6197 min = isNaN(dt) ? undefined : dt;
6198 }));
6199
6200 var max;
6201 watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
6202 var dt = new Date(value);
6203 max = isNaN(dt) ? undefined : dt;
6204 }));
6205
6206 var disabled = false;
6207 if ($attrs.ngDisabled) {
6208 watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
6209 disabled = value;
6210 }));
6211 }
6212
6213 $scope.noIncrementHours = function() {
6214 var incrementedSelected = addMinutes(selected, hourStep * 60);
6215 return disabled || incrementedSelected > max ||
6216 incrementedSelected < selected && incrementedSelected < min;
6217 };
6218
6219 $scope.noDecrementHours = function() {
6220 var decrementedSelected = addMinutes(selected, -hourStep * 60);
6221 return disabled || decrementedSelected < min ||
6222 decrementedSelected > selected && decrementedSelected > max;
6223 };
6224
6225 $scope.noIncrementMinutes = function() {
6226 var incrementedSelected = addMinutes(selected, minuteStep);
6227 return disabled || incrementedSelected > max ||
6228 incrementedSelected < selected && incrementedSelected < min;
6229 };
6230
6231 $scope.noDecrementMinutes = function() {
6232 var decrementedSelected = addMinutes(selected, -minuteStep);
6233 return disabled || decrementedSelected < min ||
6234 decrementedSelected > selected && decrementedSelected > max;
6235 };
6236
6237 $scope.noIncrementSeconds = function() {
6238 var incrementedSelected = addSeconds(selected, secondStep);
6239 return disabled || incrementedSelected > max ||
6240 incrementedSelected < selected && incrementedSelected < min;
6241 };
6242
6243 $scope.noDecrementSeconds = function() {
6244 var decrementedSelected = addSeconds(selected, -secondStep);
6245 return disabled || decrementedSelected < min ||
6246 decrementedSelected > selected && decrementedSelected > max;
6247 };
6248
6249 $scope.noToggleMeridian = function() {
6250 if (selected.getHours() < 12) {
6251 return disabled || addMinutes(selected, 12 * 60) > max;
6252 }
6253
6254 return disabled || addMinutes(selected, -12 * 60) < min;
6255 };
6256
6257 var secondStep = timepickerConfig.secondStep;
6258 if ($attrs.secondStep) {
6259 watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
6260 secondStep = +value;
6261 }));
6262 }
6263
6264 $scope.showSeconds = timepickerConfig.showSeconds;
6265 if ($attrs.showSeconds) {
6266 watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
6267 $scope.showSeconds = !!value;
6268 }));
6269 }
6270
6271 // 12H / 24H mode
6272 $scope.showMeridian = timepickerConfig.showMeridian;
6273 if ($attrs.showMeridian) {
6274 watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
6275 $scope.showMeridian = !!value;
6276
6277 if (ngModelCtrl.$error.time) {
6278 // Evaluate from template
6279 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
6280 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6281 selected.setHours(hours);
6282 refresh();
6283 }
6284 } else {
6285 updateTemplate();
6286 }
6287 }));
6288 }
6289
6290 // Get $scope.hours in 24H mode if valid
6291 function getHoursFromTemplate() {
6292 var hours = +$scope.hours;
6293 var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
6294 hours >= 0 && hours < 24;
6295 if (!valid || $scope.hours === '') {
6296 return undefined;
6297 }
6298
6299 if ($scope.showMeridian) {
6300 if (hours === 12) {
6301 hours = 0;
6302 }
6303 if ($scope.meridian === meridians[1]) {
6304 hours = hours + 12;
6305 }
6306 }
6307 return hours;
6308 }
6309
6310 function getMinutesFromTemplate() {
6311 var minutes = +$scope.minutes;
6312 var valid = minutes >= 0 && minutes < 60;
6313 if (!valid || $scope.minutes === '') {
6314 return undefined;
6315 }
6316 return minutes;
6317 }
6318
6319 function getSecondsFromTemplate() {
6320 var seconds = +$scope.seconds;
6321 return seconds >= 0 && seconds < 60 ? seconds : undefined;
6322 }
6323
6324 function pad(value, noPad) {
6325 if (value === null) {
6326 return '';
6327 }
6328
6329 return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
6330 '0' + value : value.toString();
6331 }
6332
6333 // Respond on mousewheel spin
6334 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
6335 var isScrollingUp = function(e) {
6336 if (e.originalEvent) {
6337 e = e.originalEvent;
6338 }
6339 //pick correct delta variable depending on event
6340 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
6341 return e.detail || delta > 0;
6342 };
6343
Ed Tanous4758d5b2017-06-06 15:28:13 -07006344 hoursInputEl.on('mousewheel wheel', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006345 if (!disabled) {
6346 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
6347 }
6348 e.preventDefault();
6349 });
6350
Ed Tanous4758d5b2017-06-06 15:28:13 -07006351 minutesInputEl.on('mousewheel wheel', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006352 if (!disabled) {
6353 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
6354 }
6355 e.preventDefault();
6356 });
6357
Ed Tanous4758d5b2017-06-06 15:28:13 -07006358 secondsInputEl.on('mousewheel wheel', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006359 if (!disabled) {
6360 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
6361 }
6362 e.preventDefault();
6363 });
6364 };
6365
6366 // Respond on up/down arrowkeys
6367 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006368 hoursInputEl.on('keydown', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006369 if (!disabled) {
6370 if (e.which === 38) { // up
6371 e.preventDefault();
6372 $scope.incrementHours();
6373 $scope.$apply();
6374 } else if (e.which === 40) { // down
6375 e.preventDefault();
6376 $scope.decrementHours();
6377 $scope.$apply();
6378 }
6379 }
6380 });
6381
Ed Tanous4758d5b2017-06-06 15:28:13 -07006382 minutesInputEl.on('keydown', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006383 if (!disabled) {
6384 if (e.which === 38) { // up
6385 e.preventDefault();
6386 $scope.incrementMinutes();
6387 $scope.$apply();
6388 } else if (e.which === 40) { // down
6389 e.preventDefault();
6390 $scope.decrementMinutes();
6391 $scope.$apply();
6392 }
6393 }
6394 });
6395
Ed Tanous4758d5b2017-06-06 15:28:13 -07006396 secondsInputEl.on('keydown', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006397 if (!disabled) {
6398 if (e.which === 38) { // up
6399 e.preventDefault();
6400 $scope.incrementSeconds();
6401 $scope.$apply();
6402 } else if (e.which === 40) { // down
6403 e.preventDefault();
6404 $scope.decrementSeconds();
6405 $scope.$apply();
6406 }
6407 }
6408 });
6409 };
6410
6411 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
6412 if ($scope.readonlyInput) {
6413 $scope.updateHours = angular.noop;
6414 $scope.updateMinutes = angular.noop;
6415 $scope.updateSeconds = angular.noop;
6416 return;
6417 }
6418
6419 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
6420 ngModelCtrl.$setViewValue(null);
6421 ngModelCtrl.$setValidity('time', false);
6422 if (angular.isDefined(invalidHours)) {
6423 $scope.invalidHours = invalidHours;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006424 if (hoursModelCtrl) {
6425 hoursModelCtrl.$setValidity('hours', false);
6426 }
Ed Tanous904063f2017-03-02 16:48:24 -08006427 }
6428
6429 if (angular.isDefined(invalidMinutes)) {
6430 $scope.invalidMinutes = invalidMinutes;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006431 if (minutesModelCtrl) {
6432 minutesModelCtrl.$setValidity('minutes', false);
6433 }
Ed Tanous904063f2017-03-02 16:48:24 -08006434 }
6435
6436 if (angular.isDefined(invalidSeconds)) {
6437 $scope.invalidSeconds = invalidSeconds;
Ed Tanous4758d5b2017-06-06 15:28:13 -07006438 if (secondsModelCtrl) {
6439 secondsModelCtrl.$setValidity('seconds', false);
6440 }
Ed Tanous904063f2017-03-02 16:48:24 -08006441 }
6442 };
6443
6444 $scope.updateHours = function() {
6445 var hours = getHoursFromTemplate(),
6446 minutes = getMinutesFromTemplate();
6447
6448 ngModelCtrl.$setDirty();
6449
6450 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6451 selected.setHours(hours);
6452 selected.setMinutes(minutes);
6453 if (selected < min || selected > max) {
6454 invalidate(true);
6455 } else {
6456 refresh('h');
6457 }
6458 } else {
6459 invalidate(true);
6460 }
6461 };
6462
Ed Tanous4758d5b2017-06-06 15:28:13 -07006463 hoursInputEl.on('blur', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006464 ngModelCtrl.$setTouched();
6465 if (modelIsEmpty()) {
6466 makeValid();
6467 } else if ($scope.hours === null || $scope.hours === '') {
6468 invalidate(true);
6469 } else if (!$scope.invalidHours && $scope.hours < 10) {
6470 $scope.$apply(function() {
6471 $scope.hours = pad($scope.hours, !padHours);
6472 });
6473 }
6474 });
6475
6476 $scope.updateMinutes = function() {
6477 var minutes = getMinutesFromTemplate(),
6478 hours = getHoursFromTemplate();
6479
6480 ngModelCtrl.$setDirty();
6481
6482 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6483 selected.setHours(hours);
6484 selected.setMinutes(minutes);
6485 if (selected < min || selected > max) {
6486 invalidate(undefined, true);
6487 } else {
6488 refresh('m');
6489 }
6490 } else {
6491 invalidate(undefined, true);
6492 }
6493 };
6494
Ed Tanous4758d5b2017-06-06 15:28:13 -07006495 minutesInputEl.on('blur', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006496 ngModelCtrl.$setTouched();
6497 if (modelIsEmpty()) {
6498 makeValid();
6499 } else if ($scope.minutes === null) {
6500 invalidate(undefined, true);
6501 } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
6502 $scope.$apply(function() {
6503 $scope.minutes = pad($scope.minutes);
6504 });
6505 }
6506 });
6507
6508 $scope.updateSeconds = function() {
6509 var seconds = getSecondsFromTemplate();
6510
6511 ngModelCtrl.$setDirty();
6512
6513 if (angular.isDefined(seconds)) {
6514 selected.setSeconds(seconds);
6515 refresh('s');
6516 } else {
6517 invalidate(undefined, undefined, true);
6518 }
6519 };
6520
Ed Tanous4758d5b2017-06-06 15:28:13 -07006521 secondsInputEl.on('blur', function(e) {
Ed Tanous904063f2017-03-02 16:48:24 -08006522 if (modelIsEmpty()) {
6523 makeValid();
6524 } else if (!$scope.invalidSeconds && $scope.seconds < 10) {
6525 $scope.$apply( function() {
6526 $scope.seconds = pad($scope.seconds);
6527 });
6528 }
6529 });
6530
6531 };
6532
6533 this.render = function() {
6534 var date = ngModelCtrl.$viewValue;
6535
6536 if (isNaN(date)) {
6537 ngModelCtrl.$setValidity('time', false);
6538 $log.error('Timepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
6539 } else {
6540 if (date) {
6541 selected = date;
6542 }
6543
6544 if (selected < min || selected > max) {
6545 ngModelCtrl.$setValidity('time', false);
6546 $scope.invalidHours = true;
6547 $scope.invalidMinutes = true;
6548 } else {
6549 makeValid();
6550 }
6551 updateTemplate();
6552 }
6553 };
6554
6555 // Call internally when we know that model is valid.
6556 function refresh(keyboardChange) {
6557 makeValid();
6558 ngModelCtrl.$setViewValue(new Date(selected));
6559 updateTemplate(keyboardChange);
6560 }
6561
6562 function makeValid() {
Ed Tanous4758d5b2017-06-06 15:28:13 -07006563 if (hoursModelCtrl) {
6564 hoursModelCtrl.$setValidity('hours', true);
6565 }
6566
6567 if (minutesModelCtrl) {
6568 minutesModelCtrl.$setValidity('minutes', true);
6569 }
6570
6571 if (secondsModelCtrl) {
6572 secondsModelCtrl.$setValidity('seconds', true);
6573 }
6574
Ed Tanous904063f2017-03-02 16:48:24 -08006575 ngModelCtrl.$setValidity('time', true);
6576 $scope.invalidHours = false;
6577 $scope.invalidMinutes = false;
6578 $scope.invalidSeconds = false;
6579 }
6580
6581 function updateTemplate(keyboardChange) {
6582 if (!ngModelCtrl.$modelValue) {
6583 $scope.hours = null;
6584 $scope.minutes = null;
6585 $scope.seconds = null;
6586 $scope.meridian = meridians[0];
6587 } else {
6588 var hours = selected.getHours(),
6589 minutes = selected.getMinutes(),
6590 seconds = selected.getSeconds();
6591
6592 if ($scope.showMeridian) {
6593 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
6594 }
6595
6596 $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
6597 if (keyboardChange !== 'm') {
6598 $scope.minutes = pad(minutes);
6599 }
6600 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6601
6602 if (keyboardChange !== 's') {
6603 $scope.seconds = pad(seconds);
6604 }
6605 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6606 }
6607 }
6608
6609 function addSecondsToSelected(seconds) {
6610 selected = addSeconds(selected, seconds);
6611 refresh();
6612 }
6613
6614 function addMinutes(selected, minutes) {
6615 return addSeconds(selected, minutes*60);
6616 }
6617
6618 function addSeconds(date, seconds) {
6619 var dt = new Date(date.getTime() + seconds * 1000);
6620 var newDate = new Date(date);
6621 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
6622 return newDate;
6623 }
6624
6625 function modelIsEmpty() {
6626 return ($scope.hours === null || $scope.hours === '') &&
6627 ($scope.minutes === null || $scope.minutes === '') &&
6628 (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
6629 }
6630
6631 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
6632 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
6633
6634 $scope.incrementHours = function() {
6635 if (!$scope.noIncrementHours()) {
6636 addSecondsToSelected(hourStep * 60 * 60);
6637 }
6638 };
6639
6640 $scope.decrementHours = function() {
6641 if (!$scope.noDecrementHours()) {
6642 addSecondsToSelected(-hourStep * 60 * 60);
6643 }
6644 };
6645
6646 $scope.incrementMinutes = function() {
6647 if (!$scope.noIncrementMinutes()) {
6648 addSecondsToSelected(minuteStep * 60);
6649 }
6650 };
6651
6652 $scope.decrementMinutes = function() {
6653 if (!$scope.noDecrementMinutes()) {
6654 addSecondsToSelected(-minuteStep * 60);
6655 }
6656 };
6657
6658 $scope.incrementSeconds = function() {
6659 if (!$scope.noIncrementSeconds()) {
6660 addSecondsToSelected(secondStep);
6661 }
6662 };
6663
6664 $scope.decrementSeconds = function() {
6665 if (!$scope.noDecrementSeconds()) {
6666 addSecondsToSelected(-secondStep);
6667 }
6668 };
6669
6670 $scope.toggleMeridian = function() {
6671 var minutes = getMinutesFromTemplate(),
6672 hours = getHoursFromTemplate();
6673
6674 if (!$scope.noToggleMeridian()) {
6675 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6676 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
6677 } else {
6678 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
6679 }
6680 }
6681 };
6682
6683 $scope.blur = function() {
6684 ngModelCtrl.$setTouched();
6685 };
6686
6687 $scope.$on('$destroy', function() {
6688 while (watchers.length) {
6689 watchers.shift()();
6690 }
6691 });
6692}])
6693
6694.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
6695 return {
6696 require: ['uibTimepicker', '?^ngModel'],
6697 restrict: 'A',
6698 controller: 'UibTimepickerController',
6699 controllerAs: 'timepicker',
6700 scope: {},
6701 templateUrl: function(element, attrs) {
6702 return attrs.templateUrl || uibTimepickerConfig.templateUrl;
6703 },
6704 link: function(scope, element, attrs, ctrls) {
6705 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
6706
6707 if (ngModelCtrl) {
6708 timepickerCtrl.init(ngModelCtrl, element.find('input'));
6709 }
6710 }
6711 };
6712}]);
6713
6714angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
6715
6716/**
6717 * A helper service that can parse typeahead's syntax (string provided by users)
6718 * Extracted to a separate service for ease of unit testing
6719 */
6720 .factory('uibTypeaheadParser', ['$parse', function($parse) {
6721 // 000001111111100000000000002222222200000000000000003333333333333330000000000044444444000
6722 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
6723 return {
6724 parse: function(input) {
6725 var match = input.match(TYPEAHEAD_REGEXP);
6726 if (!match) {
6727 throw new Error(
6728 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
6729 ' but got "' + input + '".');
6730 }
6731
6732 return {
6733 itemName: match[3],
6734 source: $parse(match[4]),
6735 viewMapper: $parse(match[2] || match[1]),
6736 modelMapper: $parse(match[1])
6737 };
6738 }
6739 };
6740 }])
6741
6742 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
6743 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
6744 var HOT_KEYS = [9, 13, 27, 38, 40];
6745 var eventDebounceTime = 200;
6746 var modelCtrl, ngModelOptions;
6747 //SUPPORTED ATTRIBUTES (OPTIONS)
6748
6749 //minimal no of characters that needs to be entered before typeahead kicks-in
6750 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
6751 if (!minLength && minLength !== 0) {
6752 minLength = 1;
6753 }
6754
6755 originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
6756 minLength = !newVal && newVal !== 0 ? 1 : newVal;
6757 });
6758
6759 //minimal wait time after last character typed before typeahead kicks-in
6760 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
6761
6762 //should it restrict model values to the ones selected from the popup only?
6763 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
6764 originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
6765 isEditable = newVal !== false;
6766 });
6767
6768 //binding to a variable that indicates if matches are being retrieved asynchronously
6769 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
6770
6771 //a function to determine if an event should cause selection
6772 var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) {
6773 var evt = vals.$event;
6774 return evt.which === 13 || evt.which === 9;
6775 };
6776
6777 //a callback executed when a match is selected
6778 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
6779
6780 //should it select highlighted popup value when losing focus?
6781 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
6782
6783 //binding to a variable that indicates if there were no results after the query is completed
6784 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
6785
6786 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
6787
6788 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
6789
6790 var appendTo = attrs.typeaheadAppendTo ?
6791 originalScope.$eval(attrs.typeaheadAppendTo) : null;
6792
6793 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
6794
6795 //If input matches an item of the list exactly, select it automatically
6796 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
6797
6798 //binding to a variable that indicates if dropdown is open
6799 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
6800
6801 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
6802
6803 //INTERNAL VARIABLES
6804
6805 //model setter executed upon match selection
6806 var parsedModel = $parse(attrs.ngModel);
6807 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
6808 var $setModelValue = function(scope, newValue) {
6809 if (angular.isFunction(parsedModel(originalScope)) &&
Ed Tanous4758d5b2017-06-06 15:28:13 -07006810 ngModelOptions.getOption('getterSetter')) {
Ed Tanous904063f2017-03-02 16:48:24 -08006811 return invokeModelSetter(scope, {$$$p: newValue});
6812 }
6813
6814 return parsedModel.assign(scope, newValue);
6815 };
6816
6817 //expressions used by typeahead
6818 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
6819
6820 var hasFocus;
6821
6822 //Used to avoid bug in iOS webview where iOS keyboard does not fire
6823 //mousedown & mouseup events
6824 //Issue #3699
6825 var selected;
6826
6827 //create a child scope for the typeahead directive so we are not polluting original scope
6828 //with typeahead-specific data (matches, query etc.)
6829 var scope = originalScope.$new();
6830 var offDestroy = originalScope.$on('$destroy', function() {
6831 scope.$destroy();
6832 });
6833 scope.$on('$destroy', offDestroy);
6834
6835 // WAI-ARIA
6836 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
6837 element.attr({
6838 'aria-autocomplete': 'list',
6839 'aria-expanded': false,
6840 'aria-owns': popupId
6841 });
6842
6843 var inputsContainer, hintInputElem;
6844 //add read-only input to show hint
6845 if (showHint) {
6846 inputsContainer = angular.element('<div></div>');
6847 inputsContainer.css('position', 'relative');
6848 element.after(inputsContainer);
6849 hintInputElem = element.clone();
6850 hintInputElem.attr('placeholder', '');
6851 hintInputElem.attr('tabindex', '-1');
6852 hintInputElem.val('');
6853 hintInputElem.css({
6854 'position': 'absolute',
6855 'top': '0px',
6856 'left': '0px',
6857 'border-color': 'transparent',
6858 'box-shadow': 'none',
6859 'opacity': 1,
6860 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
6861 'color': '#999'
6862 });
6863 element.css({
6864 'position': 'relative',
6865 'vertical-align': 'top',
6866 'background-color': 'transparent'
6867 });
6868
6869 if (hintInputElem.attr('id')) {
6870 hintInputElem.removeAttr('id'); // remove duplicate id if present.
6871 }
6872 inputsContainer.append(hintInputElem);
6873 hintInputElem.after(element);
6874 }
6875
6876 //pop-up element used to display matches
6877 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
6878 popUpEl.attr({
6879 id: popupId,
6880 matches: 'matches',
6881 active: 'activeIdx',
6882 select: 'select(activeIdx, evt)',
6883 'move-in-progress': 'moveInProgress',
6884 query: 'query',
6885 position: 'position',
6886 'assign-is-open': 'assignIsOpen(isOpen)',
6887 debounce: 'debounceUpdate'
6888 });
6889 //custom item template
6890 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
6891 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
6892 }
6893
6894 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
6895 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
6896 }
6897
6898 var resetHint = function() {
6899 if (showHint) {
6900 hintInputElem.val('');
6901 }
6902 };
6903
6904 var resetMatches = function() {
6905 scope.matches = [];
6906 scope.activeIdx = -1;
6907 element.attr('aria-expanded', false);
6908 resetHint();
6909 };
6910
6911 var getMatchId = function(index) {
6912 return popupId + '-option-' + index;
6913 };
6914
6915 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
6916 // This attribute is added or removed automatically when the `activeIdx` changes.
6917 scope.$watch('activeIdx', function(index) {
6918 if (index < 0) {
6919 element.removeAttr('aria-activedescendant');
6920 } else {
6921 element.attr('aria-activedescendant', getMatchId(index));
6922 }
6923 });
6924
6925 var inputIsExactMatch = function(inputValue, index) {
6926 if (scope.matches.length > index && inputValue) {
6927 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
6928 }
6929
6930 return false;
6931 };
6932
6933 var getMatchesAsync = function(inputValue, evt) {
6934 var locals = {$viewValue: inputValue};
6935 isLoadingSetter(originalScope, true);
6936 isNoResultsSetter(originalScope, false);
6937 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
6938 //it might happen that several async queries were in progress if a user were typing fast
6939 //but we are interested only in responses that correspond to the current view value
6940 var onCurrentRequest = inputValue === modelCtrl.$viewValue;
6941 if (onCurrentRequest && hasFocus) {
6942 if (matches && matches.length > 0) {
6943 scope.activeIdx = focusFirst ? 0 : -1;
6944 isNoResultsSetter(originalScope, false);
6945 scope.matches.length = 0;
6946
6947 //transform labels
6948 for (var i = 0; i < matches.length; i++) {
6949 locals[parserResult.itemName] = matches[i];
6950 scope.matches.push({
6951 id: getMatchId(i),
6952 label: parserResult.viewMapper(scope, locals),
6953 model: matches[i]
6954 });
6955 }
6956
6957 scope.query = inputValue;
6958 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
6959 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
6960 //due to other elements being rendered
6961 recalculatePosition();
6962
6963 element.attr('aria-expanded', true);
6964
6965 //Select the single remaining option if user input matches
6966 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
6967 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
6968 $$debounce(function() {
6969 scope.select(0, evt);
6970 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
6971 } else {
6972 scope.select(0, evt);
6973 }
6974 }
6975
6976 if (showHint) {
6977 var firstLabel = scope.matches[0].label;
6978 if (angular.isString(inputValue) &&
6979 inputValue.length > 0 &&
6980 firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
6981 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
6982 } else {
6983 hintInputElem.val('');
6984 }
6985 }
6986 } else {
6987 resetMatches();
6988 isNoResultsSetter(originalScope, true);
6989 }
6990 }
6991 if (onCurrentRequest) {
6992 isLoadingSetter(originalScope, false);
6993 }
6994 }, function() {
6995 resetMatches();
6996 isLoadingSetter(originalScope, false);
6997 isNoResultsSetter(originalScope, true);
6998 });
6999 };
7000
7001 // bind events only if appendToBody params exist - performance feature
7002 if (appendToBody) {
7003 angular.element($window).on('resize', fireRecalculating);
7004 $document.find('body').on('scroll', fireRecalculating);
7005 }
7006
7007 // Declare the debounced function outside recalculating for
7008 // proper debouncing
7009 var debouncedRecalculate = $$debounce(function() {
7010 // if popup is visible
7011 if (scope.matches.length) {
7012 recalculatePosition();
7013 }
7014
7015 scope.moveInProgress = false;
7016 }, eventDebounceTime);
7017
7018 // Default progress type
7019 scope.moveInProgress = false;
7020
7021 function fireRecalculating() {
7022 if (!scope.moveInProgress) {
7023 scope.moveInProgress = true;
7024 scope.$digest();
7025 }
7026
7027 debouncedRecalculate();
7028 }
7029
7030 // recalculate actual position and set new values to scope
7031 // after digest loop is popup in right position
7032 function recalculatePosition() {
7033 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
7034 scope.position.top += element.prop('offsetHeight');
7035 }
7036
7037 //we need to propagate user's query so we can higlight matches
7038 scope.query = undefined;
7039
7040 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
7041 var timeoutPromise;
7042
7043 var scheduleSearchWithTimeout = function(inputValue) {
7044 timeoutPromise = $timeout(function() {
7045 getMatchesAsync(inputValue);
7046 }, waitTime);
7047 };
7048
7049 var cancelPreviousTimeout = function() {
7050 if (timeoutPromise) {
7051 $timeout.cancel(timeoutPromise);
7052 }
7053 };
7054
7055 resetMatches();
7056
7057 scope.assignIsOpen = function (isOpen) {
7058 isOpenSetter(originalScope, isOpen);
7059 };
7060
7061 scope.select = function(activeIdx, evt) {
7062 //called from within the $digest() cycle
7063 var locals = {};
7064 var model, item;
7065
7066 selected = true;
7067 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
7068 model = parserResult.modelMapper(originalScope, locals);
7069 $setModelValue(originalScope, model);
7070 modelCtrl.$setValidity('editable', true);
7071 modelCtrl.$setValidity('parse', true);
7072
7073 onSelectCallback(originalScope, {
7074 $item: item,
7075 $model: model,
7076 $label: parserResult.viewMapper(originalScope, locals),
7077 $event: evt
7078 });
7079
7080 resetMatches();
7081
7082 //return focus to the input element if a match was selected via a mouse click event
7083 // use timeout to avoid $rootScope:inprog error
7084 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
7085 $timeout(function() { element[0].focus(); }, 0, false);
7086 }
7087 };
7088
7089 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
7090 element.on('keydown', function(evt) {
7091 //typeahead is open and an "interesting" key was pressed
7092 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
7093 return;
7094 }
7095
7096 var shouldSelect = isSelectEvent(originalScope, {$event: evt});
7097
7098 /**
7099 * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
7100 * or
7101 * shift + tab is pressed to bring focus to the previous element
7102 * then clear the results
7103 */
7104 if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) {
7105 resetMatches();
7106 scope.$digest();
7107 return;
7108 }
7109
7110 evt.preventDefault();
7111 var target;
7112 switch (evt.which) {
7113 case 27: // escape
7114 evt.stopPropagation();
7115
7116 resetMatches();
7117 originalScope.$digest();
7118 break;
7119 case 38: // up arrow
7120 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
7121 scope.$digest();
7122 target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
7123 target.parentNode.scrollTop = target.offsetTop;
7124 break;
7125 case 40: // down arrow
7126 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
7127 scope.$digest();
7128 target = popUpEl[0].querySelectorAll('.uib-typeahead-match')[scope.activeIdx];
7129 target.parentNode.scrollTop = target.offsetTop;
7130 break;
7131 default:
7132 if (shouldSelect) {
7133 scope.$apply(function() {
7134 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
7135 $$debounce(function() {
7136 scope.select(scope.activeIdx, evt);
7137 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
7138 } else {
7139 scope.select(scope.activeIdx, evt);
7140 }
7141 });
7142 }
7143 }
7144 });
7145
Ed Tanous4758d5b2017-06-06 15:28:13 -07007146 element.on('focus', function (evt) {
Ed Tanous904063f2017-03-02 16:48:24 -08007147 hasFocus = true;
7148 if (minLength === 0 && !modelCtrl.$viewValue) {
7149 $timeout(function() {
7150 getMatchesAsync(modelCtrl.$viewValue, evt);
7151 }, 0);
7152 }
7153 });
7154
Ed Tanous4758d5b2017-06-06 15:28:13 -07007155 element.on('blur', function(evt) {
Ed Tanous904063f2017-03-02 16:48:24 -08007156 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
7157 selected = true;
7158 scope.$apply(function() {
7159 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
7160 $$debounce(function() {
7161 scope.select(scope.activeIdx, evt);
7162 }, scope.debounceUpdate.blur);
7163 } else {
7164 scope.select(scope.activeIdx, evt);
7165 }
7166 });
7167 }
7168 if (!isEditable && modelCtrl.$error.editable) {
7169 modelCtrl.$setViewValue();
7170 scope.$apply(function() {
7171 // Reset validity as we are clearing
7172 modelCtrl.$setValidity('editable', true);
7173 modelCtrl.$setValidity('parse', true);
7174 });
7175 element.val('');
7176 }
7177 hasFocus = false;
7178 selected = false;
7179 });
7180
7181 // Keep reference to click handler to unbind it.
7182 var dismissClickHandler = function(evt) {
7183 // Issue #3973
7184 // Firefox treats right click as a click on document
7185 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
7186 resetMatches();
7187 if (!$rootScope.$$phase) {
7188 originalScope.$digest();
7189 }
7190 }
7191 };
7192
7193 $document.on('click', dismissClickHandler);
7194
7195 originalScope.$on('$destroy', function() {
7196 $document.off('click', dismissClickHandler);
7197 if (appendToBody || appendTo) {
7198 $popup.remove();
7199 }
7200
7201 if (appendToBody) {
7202 angular.element($window).off('resize', fireRecalculating);
7203 $document.find('body').off('scroll', fireRecalculating);
7204 }
7205 // Prevent jQuery cache memory leak
7206 popUpEl.remove();
7207
7208 if (showHint) {
7209 inputsContainer.remove();
7210 }
7211 });
7212
7213 var $popup = $compile(popUpEl)(scope);
7214
7215 if (appendToBody) {
7216 $document.find('body').append($popup);
7217 } else if (appendTo) {
7218 angular.element(appendTo).eq(0).append($popup);
7219 } else {
7220 element.after($popup);
7221 }
7222
Ed Tanous4758d5b2017-06-06 15:28:13 -07007223 this.init = function(_modelCtrl) {
Ed Tanous904063f2017-03-02 16:48:24 -08007224 modelCtrl = _modelCtrl;
Ed Tanous4758d5b2017-06-06 15:28:13 -07007225 ngModelOptions = extractOptions(modelCtrl);
Ed Tanous904063f2017-03-02 16:48:24 -08007226
Ed Tanous4758d5b2017-06-06 15:28:13 -07007227 scope.debounceUpdate = $parse(ngModelOptions.getOption('debounce'))(originalScope);
Ed Tanous904063f2017-03-02 16:48:24 -08007228
7229 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
7230 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
7231 modelCtrl.$parsers.unshift(function(inputValue) {
7232 hasFocus = true;
7233
7234 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
7235 if (waitTime > 0) {
7236 cancelPreviousTimeout();
7237 scheduleSearchWithTimeout(inputValue);
7238 } else {
7239 getMatchesAsync(inputValue);
7240 }
7241 } else {
7242 isLoadingSetter(originalScope, false);
7243 cancelPreviousTimeout();
7244 resetMatches();
7245 }
7246
7247 if (isEditable) {
7248 return inputValue;
7249 }
7250
7251 if (!inputValue) {
7252 // Reset in case user had typed something previously.
7253 modelCtrl.$setValidity('editable', true);
7254 return null;
7255 }
7256
7257 modelCtrl.$setValidity('editable', false);
7258 return undefined;
7259 });
7260
7261 modelCtrl.$formatters.push(function(modelValue) {
7262 var candidateViewValue, emptyViewValue;
7263 var locals = {};
7264
7265 // The validity may be set to false via $parsers (see above) if
7266 // the model is restricted to selected values. If the model
7267 // is set manually it is considered to be valid.
7268 if (!isEditable) {
7269 modelCtrl.$setValidity('editable', true);
7270 }
7271
7272 if (inputFormatter) {
7273 locals.$model = modelValue;
7274 return inputFormatter(originalScope, locals);
7275 }
7276
7277 //it might happen that we don't have enough info to properly render input value
7278 //we need to check for this situation and simply return model value if we can't apply custom formatting
7279 locals[parserResult.itemName] = modelValue;
7280 candidateViewValue = parserResult.viewMapper(originalScope, locals);
7281 locals[parserResult.itemName] = undefined;
7282 emptyViewValue = parserResult.viewMapper(originalScope, locals);
7283
7284 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
7285 });
7286 };
Ed Tanous4758d5b2017-06-06 15:28:13 -07007287
7288 function extractOptions(ngModelCtrl) {
7289 var ngModelOptions;
7290
7291 if (angular.version.minor < 6) { // in angular < 1.6 $options could be missing
7292 // guarantee a value
7293 ngModelOptions = ngModelCtrl.$options || {};
7294
7295 // mimic 1.6+ api
7296 ngModelOptions.getOption = function (key) {
7297 return ngModelOptions[key];
7298 };
7299 } else { // in angular >=1.6 $options is always present
7300 ngModelOptions = ngModelCtrl.$options;
7301 }
7302
7303 return ngModelOptions;
7304 }
Ed Tanous904063f2017-03-02 16:48:24 -08007305 }])
7306
7307 .directive('uibTypeahead', function() {
7308 return {
7309 controller: 'UibTypeaheadController',
Ed Tanous4758d5b2017-06-06 15:28:13 -07007310 require: ['ngModel', 'uibTypeahead'],
Ed Tanous904063f2017-03-02 16:48:24 -08007311 link: function(originalScope, element, attrs, ctrls) {
Ed Tanous4758d5b2017-06-06 15:28:13 -07007312 ctrls[1].init(ctrls[0]);
Ed Tanous904063f2017-03-02 16:48:24 -08007313 }
7314 };
7315 })
7316
7317 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
7318 return {
7319 scope: {
7320 matches: '=',
7321 query: '=',
7322 active: '=',
7323 position: '&',
7324 moveInProgress: '=',
7325 select: '&',
7326 assignIsOpen: '&',
7327 debounce: '&'
7328 },
7329 replace: true,
7330 templateUrl: function(element, attrs) {
7331 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
7332 },
7333 link: function(scope, element, attrs) {
7334 scope.templateUrl = attrs.templateUrl;
7335
7336 scope.isOpen = function() {
7337 var isDropdownOpen = scope.matches.length > 0;
7338 scope.assignIsOpen({ isOpen: isDropdownOpen });
7339 return isDropdownOpen;
7340 };
7341
7342 scope.isActive = function(matchIdx) {
7343 return scope.active === matchIdx;
7344 };
7345
7346 scope.selectActive = function(matchIdx) {
7347 scope.active = matchIdx;
7348 };
7349
7350 scope.selectMatch = function(activeIdx, evt) {
7351 var debounce = scope.debounce();
7352 if (angular.isNumber(debounce) || angular.isObject(debounce)) {
7353 $$debounce(function() {
7354 scope.select({activeIdx: activeIdx, evt: evt});
7355 }, angular.isNumber(debounce) ? debounce : debounce['default']);
7356 } else {
7357 scope.select({activeIdx: activeIdx, evt: evt});
7358 }
7359 };
7360 }
7361 };
7362 }])
7363
7364 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
7365 return {
7366 scope: {
7367 index: '=',
7368 match: '=',
7369 query: '='
7370 },
7371 link: function(scope, element, attrs) {
7372 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
7373 $templateRequest(tplUrl).then(function(tplContent) {
7374 var tplEl = angular.element(tplContent.trim());
7375 element.replaceWith(tplEl);
7376 $compile(tplEl)(scope);
7377 });
7378 }
7379 };
7380 }])
7381
7382 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
7383 var isSanitizePresent;
7384 isSanitizePresent = $injector.has('$sanitize');
7385
7386 function escapeRegexp(queryToEscape) {
7387 // Regex: capture the whole query string and replace it with the string that will be used to match
7388 // the results, for example if the capture is "a" the result will be \a
7389 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
7390 }
7391
7392 function containsHtml(matchItem) {
7393 return /<.*>/g.test(matchItem);
7394 }
7395
7396 return function(matchItem, query) {
7397 if (!isSanitizePresent && containsHtml(matchItem)) {
7398 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
7399 }
7400 matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
7401 if (!isSanitizePresent) {
7402 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
7403 }
7404 return matchItem;
7405 };
7406 }]);
7407
7408angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
7409 $templateCache.put("uib/template/accordion/accordion-group.html",
7410 "<div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
7411 " <h4 class=\"panel-title\">\n" +
7412 " <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\" ng-disabled=\"isDisabled\" uib-tabindex-toggle><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
7413 " </h4>\n" +
7414 "</div>\n" +
7415 "<div id=\"{{::panelId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabpanel\" class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
7416 " <div class=\"panel-body\" ng-transclude></div>\n" +
7417 "</div>\n" +
7418 "");
7419}]);
7420
7421angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
7422 $templateCache.put("uib/template/accordion/accordion.html",
7423 "<div role=\"tablist\" class=\"panel-group\" ng-transclude></div>");
7424}]);
7425
7426angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
7427 $templateCache.put("uib/template/alert/alert.html",
7428 "<button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
7429 " <span aria-hidden=\"true\">&times;</span>\n" +
7430 " <span class=\"sr-only\">Close</span>\n" +
7431 "</button>\n" +
7432 "<div ng-transclude></div>\n" +
7433 "");
7434}]);
7435
7436angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
7437 $templateCache.put("uib/template/carousel/carousel.html",
7438 "<div class=\"carousel-inner\" ng-transclude></div>\n" +
7439 "<a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-class=\"{ disabled: isPrevDisabled() }\" ng-show=\"slides.length > 1\">\n" +
7440 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
7441 " <span class=\"sr-only\">previous</span>\n" +
7442 "</a>\n" +
7443 "<a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-class=\"{ disabled: isNextDisabled() }\" ng-show=\"slides.length > 1\">\n" +
7444 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
7445 " <span class=\"sr-only\">next</span>\n" +
7446 "</a>\n" +
7447 "<ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
7448 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
7449 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
7450 " </li>\n" +
7451 "</ol>\n" +
7452 "");
7453}]);
7454
7455angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
7456 $templateCache.put("uib/template/carousel/slide.html",
7457 "<div class=\"text-center\" ng-transclude></div>\n" +
7458 "");
7459}]);
7460
7461angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
7462 $templateCache.put("uib/template/datepicker/datepicker.html",
7463 "<div ng-switch=\"datepickerMode\">\n" +
7464 " <div uib-daypicker ng-switch-when=\"day\" tabindex=\"0\" class=\"uib-daypicker\"></div>\n" +
7465 " <div uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\" class=\"uib-monthpicker\"></div>\n" +
7466 " <div uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\" class=\"uib-yearpicker\"></div>\n" +
7467 "</div>\n" +
7468 "");
7469}]);
7470
7471angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
7472 $templateCache.put("uib/template/datepicker/day.html",
7473 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
7474 " <thead>\n" +
7475 " <tr>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007476 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007477 " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007478 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007479 " </tr>\n" +
7480 " <tr>\n" +
7481 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
7482 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
7483 " </tr>\n" +
7484 " </thead>\n" +
7485 " <tbody>\n" +
7486 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
7487 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
7488 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" +
7489 " id=\"{{::dt.uid}}\"\n" +
7490 " ng-class=\"::dt.customClass\">\n" +
7491 " <button type=\"button\" class=\"btn btn-default btn-sm\"\n" +
7492 " uib-is-class=\"\n" +
7493 " 'btn-info' for selectedDt,\n" +
7494 " 'active' for activeDt\n" +
7495 " on dt\"\n" +
7496 " ng-click=\"select(dt.date)\"\n" +
7497 " ng-disabled=\"::dt.disabled\"\n" +
7498 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
7499 " </td>\n" +
7500 " </tr>\n" +
7501 " </tbody>\n" +
7502 "</table>\n" +
7503 "");
7504}]);
7505
7506angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
7507 $templateCache.put("uib/template/datepicker/month.html",
7508 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
7509 " <thead>\n" +
7510 " <tr>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007511 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007512 " <th colspan=\"{{::yearHeaderColspan}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007513 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></i></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007514 " </tr>\n" +
7515 " </thead>\n" +
7516 " <tbody>\n" +
7517 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
7518 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" +
7519 " id=\"{{::dt.uid}}\"\n" +
7520 " ng-class=\"::dt.customClass\">\n" +
7521 " <button type=\"button\" class=\"btn btn-default\"\n" +
7522 " uib-is-class=\"\n" +
7523 " 'btn-info' for selectedDt,\n" +
7524 " 'active' for activeDt\n" +
7525 " on dt\"\n" +
7526 " ng-click=\"select(dt.date)\"\n" +
7527 " ng-disabled=\"::dt.disabled\"\n" +
7528 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
7529 " </td>\n" +
7530 " </tr>\n" +
7531 " </tbody>\n" +
7532 "</table>\n" +
7533 "");
7534}]);
7535
7536angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
7537 $templateCache.put("uib/template/datepicker/year.html",
7538 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
7539 " <thead>\n" +
7540 " <tr>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007541 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></i><span class=\"sr-only\">previous</span></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007542 " <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" +
Ed Tanous4758d5b2017-06-06 15:28:13 -07007543 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></i><span class=\"sr-only\">next</span></button></th>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007544 " </tr>\n" +
7545 " </thead>\n" +
7546 " <tbody>\n" +
7547 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\" role=\"row\">\n" +
7548 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" +
7549 " id=\"{{::dt.uid}}\"\n" +
7550 " ng-class=\"::dt.customClass\">\n" +
7551 " <button type=\"button\" class=\"btn btn-default\"\n" +
7552 " uib-is-class=\"\n" +
7553 " 'btn-info' for selectedDt,\n" +
7554 " 'active' for activeDt\n" +
7555 " on dt\"\n" +
7556 " ng-click=\"select(dt.date)\"\n" +
7557 " ng-disabled=\"::dt.disabled\"\n" +
7558 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
7559 " </td>\n" +
7560 " </tr>\n" +
7561 " </tbody>\n" +
7562 "</table>\n" +
7563 "");
7564}]);
7565
7566angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function($templateCache) {
7567 $templateCache.put("uib/template/datepickerPopup/popup.html",
Ed Tanous4758d5b2017-06-06 15:28:13 -07007568 "<ul role=\"presentation\" class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007569 " <li ng-transclude></li>\n" +
7570 " <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" +
7571 " <span class=\"btn-group pull-left\">\n" +
7572 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
7573 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" +
7574 " </span>\n" +
7575 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" +
7576 " </li>\n" +
7577 "</ul>\n" +
7578 "");
7579}]);
7580
7581angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) {
7582 $templateCache.put("uib/template/modal/window.html",
7583 "<div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
7584 "");
7585}]);
7586
7587angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) {
7588 $templateCache.put("uib/template/pager/pager.html",
7589 "<li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
7590 "<li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
7591 "");
7592}]);
7593
7594angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
7595 $templateCache.put("uib/template/pagination/pagination.html",
Ed Tanous4758d5b2017-06-06 15:28:13 -07007596 "<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('first')}}</a></li>\n" +
7597 "<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\" ng-disabled=\"noPrevious()||ngDisabled\" uib-tabindex-toggle>{{::getText('previous')}}</a></li>\n" +
7598 "<li role=\"menuitem\" ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\" ng-disabled=\"ngDisabled&&!page.active\" uib-tabindex-toggle>{{page.text}}</a></li>\n" +
7599 "<li role=\"menuitem\" ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('next')}}</a></li>\n" +
7600 "<li role=\"menuitem\" ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\" ng-disabled=\"noNext()||ngDisabled\" uib-tabindex-toggle>{{::getText('last')}}</a></li>\n" +
Ed Tanous904063f2017-03-02 16:48:24 -08007601 "");
7602}]);
7603
7604angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
7605 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html",
7606 "<div class=\"tooltip-arrow\"></div>\n" +
7607 "<div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
7608 "");
7609}]);
7610
7611angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
7612 $templateCache.put("uib/template/tooltip/tooltip-popup.html",
7613 "<div class=\"tooltip-arrow\"></div>\n" +
7614 "<div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
7615 "");
7616}]);
7617
7618angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
7619 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html",
7620 "<div class=\"tooltip-arrow\"></div>\n" +
7621 "<div class=\"tooltip-inner\"\n" +
7622 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
7623 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
7624 "");
7625}]);
7626
7627angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
7628 $templateCache.put("uib/template/popover/popover-html.html",
7629 "<div class=\"arrow\"></div>\n" +
7630 "\n" +
7631 "<div class=\"popover-inner\">\n" +
7632 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
7633 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
7634 "</div>\n" +
7635 "");
7636}]);
7637
7638angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
7639 $templateCache.put("uib/template/popover/popover-template.html",
7640 "<div class=\"arrow\"></div>\n" +
7641 "\n" +
7642 "<div class=\"popover-inner\">\n" +
7643 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
7644 " <div class=\"popover-content\"\n" +
7645 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
7646 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
7647 "</div>\n" +
7648 "");
7649}]);
7650
7651angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
7652 $templateCache.put("uib/template/popover/popover.html",
7653 "<div class=\"arrow\"></div>\n" +
7654 "\n" +
7655 "<div class=\"popover-inner\">\n" +
7656 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" +
7657 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
7658 "</div>\n" +
7659 "");
7660}]);
7661
7662angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
7663 $templateCache.put("uib/template/progressbar/bar.html",
7664 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
7665 "");
7666}]);
7667
7668angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
7669 $templateCache.put("uib/template/progressbar/progress.html",
7670 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
7671}]);
7672
7673angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
7674 $templateCache.put("uib/template/progressbar/progressbar.html",
7675 "<div class=\"progress\">\n" +
7676 " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" +
7677 "</div>\n" +
7678 "");
7679}]);
7680
7681angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
7682 $templateCache.put("uib/template/rating/rating.html",
7683 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\" aria-valuetext=\"{{title}}\">\n" +
7684 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
7685 " <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\"></i>\n" +
7686 "</span>\n" +
7687 "");
7688}]);
7689
7690angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
7691 $templateCache.put("uib/template/tabs/tab.html",
7692 "<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n" +
7693 " <a href ng-click=\"select($event)\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n" +
7694 "</li>\n" +
7695 "");
7696}]);
7697
7698angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
7699 $templateCache.put("uib/template/tabs/tabset.html",
7700 "<div>\n" +
7701 " <ul class=\"nav nav-{{tabset.type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
7702 " <div class=\"tab-content\">\n" +
7703 " <div class=\"tab-pane\"\n" +
7704 " ng-repeat=\"tab in tabset.tabs\"\n" +
7705 " ng-class=\"{active: tabset.active === tab.index}\"\n" +
7706 " uib-tab-content-transclude=\"tab\">\n" +
7707 " </div>\n" +
7708 " </div>\n" +
7709 "</div>\n" +
7710 "");
7711}]);
7712
7713angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
7714 $templateCache.put("uib/template/timepicker/timepicker.html",
7715 "<table class=\"uib-timepicker\">\n" +
7716 " <tbody>\n" +
7717 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
7718 " <td class=\"uib-increment hours\"><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
7719 " <td>&nbsp;</td>\n" +
7720 " <td class=\"uib-increment minutes\"><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
7721 " <td ng-show=\"showSeconds\">&nbsp;</td>\n" +
7722 " <td ng-show=\"showSeconds\" class=\"uib-increment seconds\"><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
7723 " <td ng-show=\"showMeridian\"></td>\n" +
7724 " </tr>\n" +
7725 " <tr>\n" +
7726 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" +
7727 " <input type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementHours()\" ng-blur=\"blur()\">\n" +
7728 " </td>\n" +
7729 " <td class=\"uib-separator\">:</td>\n" +
7730 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
7731 " <input type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementMinutes()\" ng-blur=\"blur()\">\n" +
7732 " </td>\n" +
7733 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" +
7734 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" +
7735 " <input type=\"text\" placeholder=\"SS\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementSeconds()\" ng-blur=\"blur()\">\n" +
7736 " </td>\n" +
7737 " <td ng-show=\"showMeridian\" class=\"uib-time am-pm\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" +
7738 " </tr>\n" +
7739 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
7740 " <td class=\"uib-decrement hours\"><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
7741 " <td>&nbsp;</td>\n" +
7742 " <td class=\"uib-decrement minutes\"><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
7743 " <td ng-show=\"showSeconds\">&nbsp;</td>\n" +
7744 " <td ng-show=\"showSeconds\" class=\"uib-decrement seconds\"><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"-1\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
7745 " <td ng-show=\"showMeridian\"></td>\n" +
7746 " </tr>\n" +
7747 " </tbody>\n" +
7748 "</table>\n" +
7749 "");
7750}]);
7751
7752angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
7753 $templateCache.put("uib/template/typeahead/typeahead-match.html",
7754 "<a href\n" +
7755 " tabindex=\"-1\"\n" +
7756 " ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"\n" +
7757 " ng-attr-title=\"{{match.label}}\"></a>\n" +
7758 "");
7759}]);
7760
7761angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
7762 $templateCache.put("uib/template/typeahead/typeahead-popup.html",
7763 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
7764 " <li class=\"uib-typeahead-match\" ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index, $event)\" role=\"option\" id=\"{{::match.id}}\">\n" +
7765 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
7766 " </li>\n" +
7767 "</ul>\n" +
7768 "");
7769}]);
7770angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); angular.$$uibCarouselCss = true; });
7771angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
7772angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
7773angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
7774angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
7775angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
7776angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });