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