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