3 * http://angular-ui.github.io/bootstrap/
5 * Version: 0.14.3 - 2015-10-23
8 angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "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.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-popup.html","template/tooltip/tooltip-popup.html","template/tooltip/tooltip-template-popup.html","template/popover/popover-html.html","template/popover/popover-template.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]);
10 angular.module('ui.bootstrap.collapse', [])
12 .directive('uibCollapse', ['$animate', '$injector', function($animate, $injector) {
13 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
15 link: function(scope, element, attrs) {
17 element.removeClass('collapse')
18 .addClass('collapsing')
19 .attr('aria-expanded', true)
20 .attr('aria-hidden', false);
23 $animateCss(element, {
26 to: { height: element[0].scrollHeight + 'px' }
27 }).start().finally(expandDone);
29 $animate.addClass(element, 'in', {
30 to: { height: element[0].scrollHeight + 'px' }
35 function expandDone() {
36 element.removeClass('collapsing')
38 .css({height: 'auto'});
42 if (!element.hasClass('collapse') && !element.hasClass('in')) {
43 return collapseDone();
47 // IMPORTANT: The height must be set before adding "collapsing" class.
48 // Otherwise, the browser attempts to animate from height 0 (in
49 // collapsing class) to the given height here.
50 .css({height: element[0].scrollHeight + 'px'})
51 // initially all panel collapse have the collapse class, this removal
52 // prevents the animation from jumping to collapsed state
53 .removeClass('collapse')
54 .addClass('collapsing')
55 .attr('aria-expanded', false)
56 .attr('aria-hidden', true);
59 $animateCss(element, {
62 }).start().finally(collapseDone);
64 $animate.removeClass(element, 'in', {
66 }).then(collapseDone);
70 function collapseDone() {
71 element.css({height: '0'}); // Required so that collapse works when animation is disabled
72 element.removeClass('collapsing')
73 .addClass('collapse');
76 scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
87 /* Deprecated collapse below */
89 angular.module('ui.bootstrap.collapse')
91 .value('$collapseSuppressWarning', false)
93 .directive('collapse', ['$animate', '$injector', '$log', '$collapseSuppressWarning', function($animate, $injector, $log, $collapseSuppressWarning) {
94 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
96 link: function(scope, element, attrs) {
97 if (!$collapseSuppressWarning) {
98 $log.warn('collapse is now deprecated. Use uib-collapse instead.');
102 element.removeClass('collapse')
103 .addClass('collapsing')
104 .attr('aria-expanded', true)
105 .attr('aria-hidden', false);
108 $animateCss(element, {
110 to: { height: element[0].scrollHeight + 'px' }
111 }).start().done(expandDone);
113 $animate.animate(element, {}, {
114 height: element[0].scrollHeight + 'px'
119 function expandDone() {
120 element.removeClass('collapsing')
121 .addClass('collapse in')
122 .css({height: 'auto'});
125 function collapse() {
126 if (!element.hasClass('collapse') && !element.hasClass('in')) {
127 return collapseDone();
131 // IMPORTANT: The height must be set before adding "collapsing" class.
132 // Otherwise, the browser attempts to animate from height 0 (in
133 // collapsing class) to the given height here.
134 .css({height: element[0].scrollHeight + 'px'})
135 // initially all panel collapse have the collapse class, this removal
136 // prevents the animation from jumping to collapsed state
137 .removeClass('collapse in')
138 .addClass('collapsing')
139 .attr('aria-expanded', false)
140 .attr('aria-hidden', true);
143 $animateCss(element, {
145 }).start().done(collapseDone);
147 $animate.animate(element, {}, {
149 }).then(collapseDone);
153 function collapseDone() {
154 element.css({height: '0'}); // Required so that collapse works when animation is disabled
155 element.removeClass('collapsing')
156 .addClass('collapse');
159 scope.$watch(attrs.collapse, function(shouldCollapse) {
160 if (shouldCollapse) {
170 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
172 .constant('uibAccordionConfig', {
176 .controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
177 // This array keeps track of the accordion groups
180 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
181 this.closeOthers = function(openGroup) {
182 var closeOthers = angular.isDefined($attrs.closeOthers) ?
183 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
185 angular.forEach(this.groups, function(group) {
186 if (group !== openGroup) {
187 group.isOpen = false;
193 // This is called from the accordion-group directive to add itself to the accordion
194 this.addGroup = function(groupScope) {
196 this.groups.push(groupScope);
198 groupScope.$on('$destroy', function(event) {
199 that.removeGroup(groupScope);
203 // This is called from the accordion-group directive when to remove itself
204 this.removeGroup = function(group) {
205 var index = this.groups.indexOf(group);
207 this.groups.splice(index, 1);
213 // The accordion directive simply sets up the directive controller
214 // and adds an accordion CSS class to itself element.
215 .directive('uibAccordion', function() {
217 controller: 'UibAccordionController',
218 controllerAs: 'accordion',
220 templateUrl: function(element, attrs) {
221 return attrs.templateUrl || 'template/accordion/accordion.html';
226 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
227 .directive('uibAccordionGroup', function() {
229 require: '^uibAccordion', // We need this directive to be inside an accordion
230 transclude: true, // It transcludes the contents of the directive into the template
231 replace: true, // The element containing the directive will be replaced with the template
232 templateUrl: function(element, attrs) {
233 return attrs.templateUrl || 'template/accordion/accordion-group.html';
236 heading: '@', // Interpolate the heading attribute onto this scope
240 controller: function() {
241 this.setHeading = function(element) {
242 this.heading = element;
245 link: function(scope, element, attrs, accordionCtrl) {
246 accordionCtrl.addGroup(scope);
248 scope.openClass = attrs.openClass || 'panel-open';
249 scope.panelClass = attrs.panelClass;
250 scope.$watch('isOpen', function(value) {
251 element.toggleClass(scope.openClass, !!value);
253 accordionCtrl.closeOthers(scope);
257 scope.toggleOpen = function($event) {
258 if (!scope.isDisabled) {
259 if (!$event || $event.which === 32) {
260 scope.isOpen = !scope.isOpen;
268 // Use accordion-heading below an accordion-group to provide a heading containing HTML
269 .directive('uibAccordionHeading', function() {
271 transclude: true, // Grab the contents to be used as the heading
272 template: '', // In effect remove this element!
274 require: '^uibAccordionGroup',
275 link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
276 // Pass the heading to the accordion-group controller
277 // so that it can be transcluded into the right place in the template
278 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
279 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
284 // Use in the accordion-group template to indicate where you want the heading to be transcluded
285 // You must provide the property on the accordion-group controller that will hold the transcluded element
286 .directive('uibAccordionTransclude', function() {
288 require: ['?^uibAccordionGroup', '?^accordionGroup'],
289 link: function(scope, element, attrs, controller) {
290 controller = controller[0] ? controller[0] : controller[1]; // Delete after we remove deprecation
291 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
293 element.find('span').html('');
294 element.find('span').append(heading);
301 /* Deprecated accordion below */
303 angular.module('ui.bootstrap.accordion')
305 .value('$accordionSuppressWarning', false)
307 .controller('AccordionController', ['$scope', '$attrs', '$controller', '$log', '$accordionSuppressWarning', function($scope, $attrs, $controller, $log, $accordionSuppressWarning) {
308 if (!$accordionSuppressWarning) {
309 $log.warn('AccordionController is now deprecated. Use UibAccordionController instead.');
312 angular.extend(this, $controller('UibAccordionController', {
318 .directive('accordion', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
321 controller: 'AccordionController',
322 controllerAs: 'accordion',
325 templateUrl: function(element, attrs) {
326 return attrs.templateUrl || 'template/accordion/accordion.html';
329 if (!$accordionSuppressWarning) {
330 $log.warn('accordion is now deprecated. Use uib-accordion instead.');
336 .directive('accordionGroup', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
338 require: '^accordion', // We need this directive to be inside an accordion
340 transclude: true, // It transcludes the contents of the directive into the template
341 replace: true, // The element containing the directive will be replaced with the template
342 templateUrl: function(element, attrs) {
343 return attrs.templateUrl || 'template/accordion/accordion-group.html';
346 heading: '@', // Interpolate the heading attribute onto this scope
350 controller: function() {
351 this.setHeading = function(element) {
352 this.heading = element;
355 link: function(scope, element, attrs, accordionCtrl) {
356 if (!$accordionSuppressWarning) {
357 $log.warn('accordion-group is now deprecated. Use uib-accordion-group instead.');
360 accordionCtrl.addGroup(scope);
362 scope.openClass = attrs.openClass || 'panel-open';
363 scope.panelClass = attrs.panelClass;
364 scope.$watch('isOpen', function(value) {
365 element.toggleClass(scope.openClass, !!value);
367 accordionCtrl.closeOthers(scope);
371 scope.toggleOpen = function($event) {
372 if (!scope.isDisabled) {
373 if (!$event || $event.which === 32) {
374 scope.isOpen = !scope.isOpen;
382 .directive('accordionHeading', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
385 transclude: true, // Grab the contents to be used as the heading
386 template: '', // In effect remove this element!
388 require: '^accordionGroup',
389 link: function(scope, element, attr, accordionGroupCtrl, transclude) {
390 if (!$accordionSuppressWarning) {
391 $log.warn('accordion-heading is now deprecated. Use uib-accordion-heading instead.');
393 // Pass the heading to the accordion-group controller
394 // so that it can be transcluded into the right place in the template
395 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
396 accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
401 .directive('accordionTransclude', ['$log', '$accordionSuppressWarning', function($log, $accordionSuppressWarning) {
403 require: '^accordionGroup',
404 link: function(scope, element, attr, controller) {
405 if (!$accordionSuppressWarning) {
406 $log.warn('accordion-transclude is now deprecated. Use uib-accordion-transclude instead.');
409 scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
411 element.find('span').html('');
412 element.find('span').append(heading);
420 angular.module('ui.bootstrap.alert', [])
422 .controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
423 $scope.closeable = !!$attrs.close;
425 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
426 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
428 if (dismissOnTimeout) {
429 $timeout(function() {
431 }, parseInt(dismissOnTimeout, 10));
435 .directive('uibAlert', function() {
437 controller: 'UibAlertController',
438 controllerAs: 'alert',
439 templateUrl: function(element, attrs) {
440 return attrs.templateUrl || 'template/alert/alert.html';
451 /* Deprecated alert below */
453 angular.module('ui.bootstrap.alert')
455 .value('$alertSuppressWarning', false)
457 .controller('AlertController', ['$scope', '$attrs', '$controller', '$log', '$alertSuppressWarning', function($scope, $attrs, $controller, $log, $alertSuppressWarning) {
458 if (!$alertSuppressWarning) {
459 $log.warn('AlertController is now deprecated. Use UibAlertController instead.');
462 angular.extend(this, $controller('UibAlertController', {
468 .directive('alert', ['$log', '$alertSuppressWarning', function($log, $alertSuppressWarning) {
470 controller: 'AlertController',
471 controllerAs: 'alert',
472 templateUrl: function(element, attrs) {
473 return attrs.templateUrl || 'template/alert/alert.html';
482 if (!$alertSuppressWarning) {
483 $log.warn('alert is now deprecated. Use uib-alert instead.');
489 angular.module('ui.bootstrap.buttons', [])
491 .constant('uibButtonConfig', {
492 activeClass: 'active',
496 .controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
497 this.activeClass = buttonConfig.activeClass || 'active';
498 this.toggleEvent = buttonConfig.toggleEvent || 'click';
501 .directive('uibBtnRadio', function() {
503 require: ['uibBtnRadio', 'ngModel'],
504 controller: 'UibButtonsController',
505 controllerAs: 'buttons',
506 link: function(scope, element, attrs, ctrls) {
507 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
509 element.find('input').css({display: 'none'});
512 ngModelCtrl.$render = function() {
513 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
517 element.on(buttonsCtrl.toggleEvent, function() {
518 if (attrs.disabled) {
522 var isActive = element.hasClass(buttonsCtrl.activeClass);
524 if (!isActive || angular.isDefined(attrs.uncheckable)) {
525 scope.$apply(function() {
526 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
527 ngModelCtrl.$render();
535 .directive('uibBtnCheckbox', function() {
537 require: ['uibBtnCheckbox', 'ngModel'],
538 controller: 'UibButtonsController',
539 controllerAs: 'button',
540 link: function(scope, element, attrs, ctrls) {
541 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
543 element.find('input').css({display: 'none'});
545 function getTrueValue() {
546 return getCheckboxValue(attrs.btnCheckboxTrue, true);
549 function getFalseValue() {
550 return getCheckboxValue(attrs.btnCheckboxFalse, false);
553 function getCheckboxValue(attribute, defaultValue) {
554 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
558 ngModelCtrl.$render = function() {
559 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
563 element.on(buttonsCtrl.toggleEvent, function() {
564 if (attrs.disabled) {
568 scope.$apply(function() {
569 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
570 ngModelCtrl.$render();
577 /* Deprecated buttons below */
579 angular.module('ui.bootstrap.buttons')
581 .value('$buttonsSuppressWarning', false)
583 .controller('ButtonsController', ['$controller', '$log', '$buttonsSuppressWarning', function($controller, $log, $buttonsSuppressWarning) {
584 if (!$buttonsSuppressWarning) {
585 $log.warn('ButtonsController is now deprecated. Use UibButtonsController instead.');
588 angular.extend(this, $controller('UibButtonsController'));
591 .directive('btnRadio', ['$log', '$buttonsSuppressWarning', function($log, $buttonsSuppressWarning) {
593 require: ['btnRadio', 'ngModel'],
594 controller: 'ButtonsController',
595 controllerAs: 'buttons',
596 link: function(scope, element, attrs, ctrls) {
597 if (!$buttonsSuppressWarning) {
598 $log.warn('btn-radio is now deprecated. Use uib-btn-radio instead.');
601 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
603 element.find('input').css({display: 'none'});
606 ngModelCtrl.$render = function() {
607 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
611 element.bind(buttonsCtrl.toggleEvent, function() {
612 if (attrs.disabled) {
616 var isActive = element.hasClass(buttonsCtrl.activeClass);
618 if (!isActive || angular.isDefined(attrs.uncheckable)) {
619 scope.$apply(function() {
620 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
621 ngModelCtrl.$render();
629 .directive('btnCheckbox', ['$document', '$log', '$buttonsSuppressWarning', function($document, $log, $buttonsSuppressWarning) {
631 require: ['btnCheckbox', 'ngModel'],
632 controller: 'ButtonsController',
633 controllerAs: 'button',
634 link: function(scope, element, attrs, ctrls) {
635 if (!$buttonsSuppressWarning) {
636 $log.warn('btn-checkbox is now deprecated. Use uib-btn-checkbox instead.');
639 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
641 element.find('input').css({display: 'none'});
643 function getTrueValue() {
644 return getCheckboxValue(attrs.btnCheckboxTrue, true);
647 function getFalseValue() {
648 return getCheckboxValue(attrs.btnCheckboxFalse, false);
651 function getCheckboxValue(attributeValue, defaultValue) {
652 var val = scope.$eval(attributeValue);
653 return angular.isDefined(val) ? val : defaultValue;
657 ngModelCtrl.$render = function() {
658 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
662 element.bind(buttonsCtrl.toggleEvent, function() {
663 if (attrs.disabled) {
667 scope.$apply(function() {
668 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
669 ngModelCtrl.$render();
674 element.on('keypress', function(e) {
675 if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) {
679 scope.$apply(function() {
680 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
681 ngModelCtrl.$render();
691 * @name ui.bootstrap.carousel
694 * AngularJS version of an image carousel.
697 angular.module('ui.bootstrap.carousel', [])
699 .controller('UibCarouselController', ['$scope', '$element', '$interval', '$animate', function($scope, $element, $interval, $animate) {
701 slides = self.slides = $scope.slides = [],
702 NEW_ANIMATE = angular.version.minor >= 4,
703 NO_TRANSITION = 'uib-noTransition',
704 SLIDE_DIRECTION = 'uib-slideDirection',
706 currentInterval, isPlaying;
707 self.currentSlide = null;
709 var destroyed = false;
710 /* direction: "prev" or "next" */
711 self.select = $scope.select = function(nextSlide, direction) {
712 var nextIndex = $scope.indexOfSlide(nextSlide);
713 //Decide direction if it's not given
714 if (direction === undefined) {
715 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
717 //Prevent this user-triggered transition from occurring if there is already one in progress
718 if (nextSlide && nextSlide !== self.currentSlide && !$scope.$currentTransition) {
719 goNext(nextSlide, nextIndex, direction);
723 function goNext(slide, index, direction) {
724 // Scope has been destroyed, stop here.
725 if (destroyed) { return; }
727 angular.extend(slide, {direction: direction, active: true});
728 angular.extend(self.currentSlide || {}, {direction: direction, active: false});
729 if ($animate.enabled() && !$scope.noTransition && !$scope.$currentTransition &&
730 slide.$element && self.slides.length > 1) {
731 slide.$element.data(SLIDE_DIRECTION, slide.direction);
732 if (self.currentSlide && self.currentSlide.$element) {
733 self.currentSlide.$element.data(SLIDE_DIRECTION, slide.direction);
736 $scope.$currentTransition = true;
738 $animate.on('addClass', slide.$element, function(element, phase) {
739 if (phase === 'close') {
740 $scope.$currentTransition = null;
741 $animate.off('addClass', element);
745 slide.$element.one('$animate:close', function closeFn() {
746 $scope.$currentTransition = null;
751 self.currentSlide = slide;
752 currentIndex = index;
754 //every time you change slides, reset the timer
758 $scope.$on('$destroy', function() {
762 function getSlideByIndex(index) {
763 if (angular.isUndefined(slides[index].index)) {
764 return slides[index];
766 var i, len = slides.length;
767 for (i = 0; i < slides.length; ++i) {
768 if (slides[i].index == index) {
774 self.getCurrentIndex = function() {
775 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
776 return +self.currentSlide.index;
781 /* Allow outside people to call indexOf on slides array */
782 $scope.indexOfSlide = function(slide) {
783 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
786 $scope.next = function() {
787 var newIndex = (self.getCurrentIndex() + 1) % slides.length;
789 if (newIndex === 0 && $scope.noWrap()) {
794 return self.select(getSlideByIndex(newIndex), 'next');
797 $scope.prev = function() {
798 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
800 if ($scope.noWrap() && newIndex === slides.length - 1) {
805 return self.select(getSlideByIndex(newIndex), 'prev');
808 $scope.isActive = function(slide) {
809 return self.currentSlide === slide;
812 $scope.$watch('interval', restartTimer);
813 $scope.$watchCollection('slides', resetTransition);
814 $scope.$on('$destroy', resetTimer);
816 function restartTimer() {
818 var interval = +$scope.interval;
819 if (!isNaN(interval) && interval > 0) {
820 currentInterval = $interval(timerFn, interval);
824 function resetTimer() {
825 if (currentInterval) {
826 $interval.cancel(currentInterval);
827 currentInterval = null;
832 var interval = +$scope.interval;
833 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
840 function resetTransition(slides) {
841 if (!slides.length) {
842 $scope.$currentTransition = null;
846 $scope.play = function() {
852 $scope.pause = function() {
853 if (!$scope.noPause) {
859 self.addSlide = function(slide, element) {
860 slide.$element = element;
862 //if this is the first slide or the slide is set to active, select it
863 if (slides.length === 1 || slide.active) {
864 self.select(slides[slides.length - 1]);
865 if (slides.length === 1) {
869 slide.active = false;
873 self.removeSlide = function(slide) {
874 if (angular.isDefined(slide.index)) {
875 slides.sort(function(a, b) {
876 return +a.index > +b.index;
879 //get the index of the slide inside the carousel
880 var index = slides.indexOf(slide);
881 slides.splice(index, 1);
882 if (slides.length > 0 && slide.active) {
883 if (index >= slides.length) {
884 self.select(slides[index - 1]);
886 self.select(slides[index]);
888 } else if (currentIndex > index) {
892 //clean the currentSlide when no more slide
893 if (slides.length === 0) {
894 self.currentSlide = null;
898 $scope.$watch('noTransition', function(noTransition) {
899 $element.data(NO_TRANSITION, noTransition);
906 * @name ui.bootstrap.carousel.directive:carousel
910 * Carousel is the outer container for a set of image 'slides' to showcase.
912 * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
913 * @param {boolean=} noTransition Whether to disable transitions on the carousel.
914 * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
917 <example module="ui.bootstrap">
918 <file name="index.html">
921 <img src="http://placekitten.com/150/150" style="margin:auto;">
922 <div class="carousel-caption">
927 <img src="http://placekitten.com/100/150" style="margin:auto;">
928 <div class="carousel-caption">
934 <file name="demo.css">
935 .carousel-indicators {
942 .directive('uibCarousel', [function() {
946 controller: 'UibCarouselController',
947 controllerAs: 'carousel',
949 templateUrl: function(element, attrs) {
950 return attrs.templateUrl || 'template/carousel/carousel.html';
963 * @name ui.bootstrap.carousel.directive:slide
967 * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
969 * @param {boolean=} active Model binding, whether or not this slide is currently active.
970 * @param {number=} index The index of the slide. The slides will be sorted by this parameter.
973 <example module="ui.bootstrap">
974 <file name="index.html">
975 <div ng-controller="CarouselDemoCtrl">
977 <uib-slide ng-repeat="slide in slides" active="slide.active" index="$index">
978 <img ng-src="{{slide.image}}" style="margin:auto;">
979 <div class="carousel-caption">
980 <h4>Slide {{$index}}</h4>
981 <p>{{slide.text}}</p>
985 Interval, in milliseconds: <input type="number" ng-model="myInterval">
986 <br />Enter a negative number to stop the interval.
989 <file name="script.js">
990 function CarouselDemoCtrl($scope) {
991 $scope.myInterval = 5000;
994 <file name="demo.css">
995 .carousel-indicators {
1003 .directive('uibSlide', function() {
1005 require: '^uibCarousel',
1009 templateUrl: function(element, attrs) {
1010 return attrs.templateUrl || 'template/carousel/slide.html';
1017 link: function (scope, element, attrs, carouselCtrl) {
1018 carouselCtrl.addSlide(scope, element);
1019 //when the scope is destroyed then remove the slide from the current slides array
1020 scope.$on('$destroy', function() {
1021 carouselCtrl.removeSlide(scope);
1024 scope.$watch('active', function(active) {
1026 carouselCtrl.select(scope);
1033 .animation('.item', [
1034 '$injector', '$animate',
1035 function ($injector, $animate) {
1036 var NO_TRANSITION = 'uib-noTransition',
1037 SLIDE_DIRECTION = 'uib-slideDirection',
1040 if ($injector.has('$animateCss')) {
1041 $animateCss = $injector.get('$animateCss');
1044 function removeClass(element, className, callback) {
1045 element.removeClass(className);
1052 beforeAddClass: function(element, className, done) {
1053 // Due to transclusion, noTransition property is on parent's scope
1054 if (className == 'active' && element.parent() && element.parent().parent() &&
1055 !element.parent().parent().data(NO_TRANSITION)) {
1056 var stopped = false;
1057 var direction = element.data(SLIDE_DIRECTION);
1058 var directionClass = direction == 'next' ? 'left' : 'right';
1059 var removeClassFn = removeClass.bind(this, element,
1060 directionClass + ' ' + direction, done);
1061 element.addClass(direction);
1064 $animateCss(element, {addClass: directionClass})
1066 .done(removeClassFn);
1068 $animate.addClass(element, directionClass).then(function () {
1076 return function () {
1082 beforeRemoveClass: function (element, className, done) {
1083 // Due to transclusion, noTransition property is on parent's scope
1084 if (className === 'active' && element.parent() && element.parent().parent() &&
1085 !element.parent().parent().data(NO_TRANSITION)) {
1086 var stopped = false;
1087 var direction = element.data(SLIDE_DIRECTION);
1088 var directionClass = direction == 'next' ? 'left' : 'right';
1089 var removeClassFn = removeClass.bind(this, element, directionClass, done);
1092 $animateCss(element, {addClass: directionClass})
1094 .done(removeClassFn);
1096 $animate.addClass(element, directionClass).then(function() {
1112 /* deprecated carousel below */
1114 angular.module('ui.bootstrap.carousel')
1116 .value('$carouselSuppressWarning', false)
1118 .controller('CarouselController', ['$scope', '$element', '$controller', '$log', '$carouselSuppressWarning', function($scope, $element, $controller, $log, $carouselSuppressWarning) {
1119 if (!$carouselSuppressWarning) {
1120 $log.warn('CarouselController is now deprecated. Use UibCarouselController instead.');
1123 angular.extend(this, $controller('UibCarouselController', {
1129 .directive('carousel', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
1133 controller: 'CarouselController',
1134 controllerAs: 'carousel',
1135 require: 'carousel',
1136 templateUrl: function(element, attrs) {
1137 return attrs.templateUrl || 'template/carousel/carousel.html';
1146 if (!$carouselSuppressWarning) {
1147 $log.warn('carousel is now deprecated. Use uib-carousel instead.');
1153 .directive('slide', ['$log', '$carouselSuppressWarning', function($log, $carouselSuppressWarning) {
1155 require: '^carousel',
1158 templateUrl: function(element, attrs) {
1159 return attrs.templateUrl || 'template/carousel/slide.html';
1166 link: function (scope, element, attrs, carouselCtrl) {
1167 if (!$carouselSuppressWarning) {
1168 $log.warn('slide is now deprecated. Use uib-slide instead.');
1171 carouselCtrl.addSlide(scope, element);
1172 //when the scope is destroyed then remove the slide from the current slides array
1173 scope.$on('$destroy', function() {
1174 carouselCtrl.removeSlide(scope);
1177 scope.$watch('active', function(active) {
1179 carouselCtrl.select(scope);
1186 angular.module('ui.bootstrap.dateparser', [])
1188 .service('uibDateParser', ['$log', '$locale', 'orderByFilter', function($log, $locale, orderByFilter) {
1189 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
1190 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
1193 var formatCodeToRegex;
1195 this.init = function() {
1196 localeId = $locale.id;
1200 formatCodeToRegex = {
1203 apply: function(value) { this.year = +value; }
1207 apply: function(value) { this.year = +value + 2000; }
1211 apply: function(value) { this.year = +value; }
1214 regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
1215 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
1218 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
1219 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
1222 regex: '0[1-9]|1[0-2]',
1223 apply: function(value) { this.month = value - 1; }
1226 regex: '[1-9]|1[0-2]',
1227 apply: function(value) { this.month = value - 1; }
1230 regex: '[0-2][0-9]{1}|3[0-1]{1}',
1231 apply: function(value) { this.date = +value; }
1234 regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
1235 apply: function(value) { this.date = +value; }
1238 regex: $locale.DATETIME_FORMATS.DAY.join('|')
1241 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
1244 regex: '(?:0|1)[0-9]|2[0-3]',
1245 apply: function(value) { this.hours = +value; }
1248 regex: '0[0-9]|1[0-2]',
1249 apply: function(value) { this.hours = +value; }
1252 regex: '1?[0-9]|2[0-3]',
1253 apply: function(value) { this.hours = +value; }
1256 regex: '[0-9]|1[0-2]',
1257 apply: function(value) { this.hours = +value; }
1260 regex: '[0-5][0-9]',
1261 apply: function(value) { this.minutes = +value; }
1264 regex: '[0-9]|[1-5][0-9]',
1265 apply: function(value) { this.minutes = +value; }
1268 regex: '[0-9][0-9][0-9]',
1269 apply: function(value) { this.milliseconds = +value; }
1272 regex: '[0-5][0-9]',
1273 apply: function(value) { this.seconds = +value; }
1276 regex: '[0-9]|[1-5][0-9]',
1277 apply: function(value) { this.seconds = +value; }
1280 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
1281 apply: function(value) {
1282 if (this.hours === 12) {
1286 if (value === 'PM') {
1296 function createParser(format) {
1297 var map = [], regex = format.split('');
1299 angular.forEach(formatCodeToRegex, function(data, code) {
1300 var index = format.indexOf(code);
1303 format = format.split('');
1305 regex[index] = '(' + data.regex + ')';
1306 format[index] = '$'; // Custom symbol to define consumed part of format
1307 for (var i = index + 1, n = index + code.length; i < n; i++) {
1311 format = format.join('');
1313 map.push({ index: index, apply: data.apply });
1318 regex: new RegExp('^' + regex.join('') + '$'),
1319 map: orderByFilter(map, 'index')
1323 this.parse = function(input, format, baseDate) {
1324 if (!angular.isString(input) || !format) {
1328 format = $locale.DATETIME_FORMATS[format] || format;
1329 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
1331 if ($locale.id !== localeId) {
1335 if (!this.parsers[format]) {
1336 this.parsers[format] = createParser(format);
1339 var parser = this.parsers[format],
1340 regex = parser.regex,
1342 results = input.match(regex);
1344 if (results && results.length) {
1346 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
1348 year: baseDate.getFullYear(),
1349 month: baseDate.getMonth(),
1350 date: baseDate.getDate(),
1351 hours: baseDate.getHours(),
1352 minutes: baseDate.getMinutes(),
1353 seconds: baseDate.getSeconds(),
1354 milliseconds: baseDate.getMilliseconds()
1358 $log.warn('dateparser:', 'baseDate is not a valid date');
1360 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
1363 for (var i = 1, n = results.length; i < n; i++) {
1364 var mapper = map[i-1];
1366 mapper.apply.call(fields, results[i]);
1370 if (isValid(fields.year, fields.month, fields.date)) {
1371 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
1372 dt = new Date(baseDate);
1373 dt.setFullYear(fields.year, fields.month, fields.date,
1374 fields.hours, fields.minutes, fields.seconds,
1375 fields.milliseconds || 0);
1377 dt = new Date(fields.year, fields.month, fields.date,
1378 fields.hours, fields.minutes, fields.seconds,
1379 fields.milliseconds || 0);
1387 // Check if date is valid for specific month (and year for February).
1388 // Month: 0 = Jan, 1 = Feb, etc
1389 function isValid(year, month, date) {
1394 if (month === 1 && date > 28) {
1395 return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
1398 if (month === 3 || month === 5 || month === 8 || month === 10) {
1406 /* Deprecated dateparser below */
1408 angular.module('ui.bootstrap.dateparser')
1410 .value('$dateParserSuppressWarning', false)
1412 .service('dateParser', ['$log', '$dateParserSuppressWarning', 'uibDateParser', function($log, $dateParserSuppressWarning, uibDateParser) {
1413 if (!$dateParserSuppressWarning) {
1414 $log.warn('dateParser is now deprecated. Use uibDateParser instead.');
1417 angular.extend(this, uibDateParser);
1420 angular.module('ui.bootstrap.position', [])
1423 * A set of utility methods that can be use to retrieve position of DOM elements.
1424 * It is meant to be used where we need to absolute-position DOM elements in
1425 * relation to other, existing elements (this is the case for tooltips, popovers,
1426 * typeahead suggestions etc.).
1428 .factory('$uibPosition', ['$document', '$window', function($document, $window) {
1429 function getStyle(el, cssprop) {
1430 if (el.currentStyle) { //IE
1431 return el.currentStyle[cssprop];
1432 } else if ($window.getComputedStyle) {
1433 return $window.getComputedStyle(el)[cssprop];
1435 // finally try and get inline style
1436 return el.style[cssprop];
1440 * Checks if a given element is statically positioned
1441 * @param element - raw DOM element
1443 function isStaticPositioned(element) {
1444 return (getStyle(element, 'position') || 'static' ) === 'static';
1448 * returns the closest, non-statically positioned parentOffset of a given element
1451 var parentOffsetEl = function(element) {
1452 var docDomEl = $document[0];
1453 var offsetParent = element.offsetParent || docDomEl;
1454 while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
1455 offsetParent = offsetParent.offsetParent;
1457 return offsetParent || docDomEl;
1462 * Provides read-only equivalent of jQuery's position function:
1463 * http://api.jquery.com/position/
1465 position: function(element) {
1466 var elBCR = this.offset(element);
1467 var offsetParentBCR = { top: 0, left: 0 };
1468 var offsetParentEl = parentOffsetEl(element[0]);
1469 if (offsetParentEl != $document[0]) {
1470 offsetParentBCR = this.offset(angular.element(offsetParentEl));
1471 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
1472 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
1475 var boundingClientRect = element[0].getBoundingClientRect();
1477 width: boundingClientRect.width || element.prop('offsetWidth'),
1478 height: boundingClientRect.height || element.prop('offsetHeight'),
1479 top: elBCR.top - offsetParentBCR.top,
1480 left: elBCR.left - offsetParentBCR.left
1485 * Provides read-only equivalent of jQuery's offset function:
1486 * http://api.jquery.com/offset/
1488 offset: function(element) {
1489 var boundingClientRect = element[0].getBoundingClientRect();
1491 width: boundingClientRect.width || element.prop('offsetWidth'),
1492 height: boundingClientRect.height || element.prop('offsetHeight'),
1493 top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
1494 left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
1499 * Provides coordinates for the targetEl in relation to hostEl
1501 positionElements: function(hostEl, targetEl, positionStr, appendToBody) {
1502 var positionStrParts = positionStr.split('-');
1503 var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
1510 hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
1512 targetElWidth = targetEl.prop('offsetWidth');
1513 targetElHeight = targetEl.prop('offsetHeight');
1516 center: function() {
1517 return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
1520 return hostElPos.left;
1523 return hostElPos.left + hostElPos.width;
1528 center: function() {
1529 return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
1532 return hostElPos.top;
1534 bottom: function() {
1535 return hostElPos.top + hostElPos.height;
1542 top: shiftHeight[pos1](),
1543 left: shiftWidth[pos0]()
1548 top: shiftHeight[pos1](),
1549 left: hostElPos.left - targetElWidth
1554 top: shiftHeight[pos0](),
1555 left: shiftWidth[pos1]()
1560 top: hostElPos.top - targetElHeight,
1561 left: shiftWidth[pos1]()
1571 /* Deprecated position below */
1573 angular.module('ui.bootstrap.position')
1575 .value('$positionSuppressWarning', false)
1577 .service('$position', ['$log', '$positionSuppressWarning', '$uibPosition', function($log, $positionSuppressWarning, $uibPosition) {
1578 if (!$positionSuppressWarning) {
1579 $log.warn('$position is now deprecated. Use $uibPosition instead.');
1582 angular.extend(this, $uibPosition);
1585 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
1587 .value('$datepickerSuppressError', false)
1589 .constant('uibDatepickerConfig', {
1591 formatMonth: 'MMMM',
1593 formatDayHeader: 'EEE',
1594 formatDayTitle: 'MMMM yyyy',
1595 formatMonthTitle: 'yyyy',
1596 datepickerMode: 'day',
1604 shortcutPropagation: false
1607 .controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError) {
1609 ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
1612 this.modes = ['day', 'month', 'year'];
1614 // Configuration attributes
1615 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
1616 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
1617 self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
1620 // Watchable date attributes
1621 angular.forEach(['minDate', 'maxDate'], function(key) {
1623 $scope.$parent.$watch($parse($attrs[key]), function(value) {
1624 self[key] = value ? new Date(value) : null;
1628 self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
1632 angular.forEach(['minMode', 'maxMode'], function(key) {
1634 $scope.$parent.$watch($parse($attrs[key]), function(value) {
1635 self[key] = angular.isDefined(value) ? value : $attrs[key];
1636 $scope[key] = self[key];
1637 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]))) {
1638 $scope.datepickerMode = self[key];
1642 self[key] = datepickerConfig[key] || null;
1643 $scope[key] = self[key];
1647 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
1648 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1650 if (angular.isDefined($attrs.initDate)) {
1651 this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
1652 $scope.$parent.$watch($attrs.initDate, function(initDate) {
1653 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
1654 self.activeDate = initDate;
1659 this.activeDate = new Date();
1662 $scope.isActive = function(dateObject) {
1663 if (self.compare(dateObject.date, self.activeDate) === 0) {
1664 $scope.activeDateId = dateObject.uid;
1670 this.init = function(ngModelCtrl_) {
1671 ngModelCtrl = ngModelCtrl_;
1673 ngModelCtrl.$render = function() {
1678 this.render = function() {
1679 if (ngModelCtrl.$viewValue) {
1680 var date = new Date(ngModelCtrl.$viewValue),
1681 isValid = !isNaN(date);
1684 this.activeDate = date;
1685 } else if (!$datepickerSuppressError) {
1686 $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.');
1692 this.refreshView = function() {
1694 this._refreshView();
1696 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1697 ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
1701 this.createDateObject = function(date, format) {
1702 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1705 label: dateFilter(date, format),
1706 selected: model && this.compare(date, model) === 0,
1707 disabled: this.isDisabled(date),
1708 current: this.compare(date, new Date()) === 0,
1709 customClass: this.customClass(date)
1713 this.isDisabled = function(date) {
1714 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})));
1717 this.customClass = function(date) {
1718 return $scope.customClass({date: date, mode: $scope.datepickerMode});
1721 // Split array into smaller arrays
1722 this.split = function(arr, size) {
1724 while (arr.length > 0) {
1725 arrays.push(arr.splice(0, size));
1730 $scope.select = function(date) {
1731 if ($scope.datepickerMode === self.minMode) {
1732 var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
1733 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
1734 ngModelCtrl.$setViewValue(dt);
1735 ngModelCtrl.$render();
1737 self.activeDate = date;
1738 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
1742 $scope.move = function(direction) {
1743 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1744 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1745 self.activeDate.setFullYear(year, month, 1);
1749 $scope.toggleMode = function(direction) {
1750 direction = direction || 1;
1752 if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
1756 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
1760 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
1762 var focusElement = function() {
1763 self.element[0].focus();
1766 // Listen for focus requests from popup directive
1767 $scope.$on('uib:datepicker.focus', focusElement);
1769 $scope.keydown = function(evt) {
1770 var key = $scope.keys[evt.which];
1772 if (!key || evt.shiftKey || evt.altKey) {
1776 evt.preventDefault();
1777 if (!self.shortcutPropagation) {
1778 evt.stopPropagation();
1781 if (key === 'enter' || key === 'space') {
1782 if (self.isDisabled(self.activeDate)) {
1783 return; // do nothing
1785 $scope.select(self.activeDate);
1786 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
1787 $scope.toggleMode(key === 'up' ? 1 : -1);
1789 self.handleKeyDown(key, evt);
1795 .controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1796 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1798 this.step = { months: 1 };
1799 this.element = $element;
1800 function getDaysInMonth(year, month) {
1801 return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
1804 this.init = function(ctrl) {
1805 angular.extend(ctrl, this);
1806 scope.showWeeks = ctrl.showWeeks;
1810 this.getDates = function(startDate, n) {
1811 var dates = new Array(n), current = new Date(startDate), i = 0, date;
1813 date = new Date(current);
1815 current.setDate(current.getDate() + 1);
1820 this._refreshView = function() {
1821 var year = this.activeDate.getFullYear(),
1822 month = this.activeDate.getMonth(),
1823 firstDayOfMonth = new Date(this.activeDate);
1825 firstDayOfMonth.setFullYear(year, month, 1);
1827 var difference = this.startingDay - firstDayOfMonth.getDay(),
1828 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
1829 firstDate = new Date(firstDayOfMonth);
1831 if (numDisplayedFromPreviousMonth > 0) {
1832 firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1835 // 42 is the number of days on a six-month calendar
1836 var days = this.getDates(firstDate, 42);
1837 for (var i = 0; i < 42; i ++) {
1838 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
1839 secondary: days[i].getMonth() !== month,
1840 uid: scope.uniqueId + '-' + i
1844 scope.labels = new Array(7);
1845 for (var j = 0; j < 7; j++) {
1847 abbr: dateFilter(days[j].date, this.formatDayHeader),
1848 full: dateFilter(days[j].date, 'EEEE')
1852 scope.title = dateFilter(this.activeDate, this.formatDayTitle);
1853 scope.rows = this.split(days, 7);
1855 if (scope.showWeeks) {
1856 scope.weekNumbers = [];
1857 var thursdayIndex = (4 + 7 - this.startingDay) % 7,
1858 numWeeks = scope.rows.length;
1859 for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1860 scope.weekNumbers.push(
1861 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
1866 this.compare = function(date1, date2) {
1867 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
1870 function getISO8601WeekNumber(date) {
1871 var checkDate = new Date(date);
1872 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1873 var time = checkDate.getTime();
1874 checkDate.setMonth(0); // Compare with Jan 1
1875 checkDate.setDate(1);
1876 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1879 this.handleKeyDown = function(key, evt) {
1880 var date = this.activeDate.getDate();
1882 if (key === 'left') {
1883 date = date - 1; // up
1884 } else if (key === 'up') {
1885 date = date - 7; // down
1886 } else if (key === 'right') {
1887 date = date + 1; // down
1888 } else if (key === 'down') {
1890 } else if (key === 'pageup' || key === 'pagedown') {
1891 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
1892 this.activeDate.setMonth(month, 1);
1893 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
1894 } else if (key === 'home') {
1896 } else if (key === 'end') {
1897 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
1899 this.activeDate.setDate(date);
1903 .controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1904 this.step = { years: 1 };
1905 this.element = $element;
1907 this.init = function(ctrl) {
1908 angular.extend(ctrl, this);
1912 this._refreshView = function() {
1913 var months = new Array(12),
1914 year = this.activeDate.getFullYear(),
1917 for (var i = 0; i < 12; i++) {
1918 date = new Date(this.activeDate);
1919 date.setFullYear(year, i, 1);
1920 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
1921 uid: scope.uniqueId + '-' + i
1925 scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
1926 scope.rows = this.split(months, 3);
1929 this.compare = function(date1, date2) {
1930 return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
1933 this.handleKeyDown = function(key, evt) {
1934 var date = this.activeDate.getMonth();
1936 if (key === 'left') {
1937 date = date - 1; // up
1938 } else if (key === 'up') {
1939 date = date - 3; // down
1940 } else if (key === 'right') {
1941 date = date + 1; // down
1942 } else if (key === 'down') {
1944 } else if (key === 'pageup' || key === 'pagedown') {
1945 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
1946 this.activeDate.setFullYear(year);
1947 } else if (key === 'home') {
1949 } else if (key === 'end') {
1952 this.activeDate.setMonth(date);
1956 .controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1958 this.element = $element;
1960 function getStartingYear(year) {
1961 return parseInt((year - 1) / range, 10) * range + 1;
1964 this.yearpickerInit = function() {
1965 range = this.yearRange;
1966 this.step = { years: range };
1969 this._refreshView = function() {
1970 var years = new Array(range), date;
1972 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
1973 date = new Date(this.activeDate);
1974 date.setFullYear(start + i, 0, 1);
1975 years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
1976 uid: scope.uniqueId + '-' + i
1980 scope.title = [years[0].label, years[range - 1].label].join(' - ');
1981 scope.rows = this.split(years, 5);
1984 this.compare = function(date1, date2) {
1985 return date1.getFullYear() - date2.getFullYear();
1988 this.handleKeyDown = function(key, evt) {
1989 var date = this.activeDate.getFullYear();
1991 if (key === 'left') {
1992 date = date - 1; // up
1993 } else if (key === 'up') {
1994 date = date - 5; // down
1995 } else if (key === 'right') {
1996 date = date + 1; // down
1997 } else if (key === 'down') {
1999 } else if (key === 'pageup' || key === 'pagedown') {
2000 date += (key === 'pageup' ? - 1 : 1) * this.step.years;
2001 } else if (key === 'home') {
2002 date = getStartingYear(this.activeDate.getFullYear());
2003 } else if (key === 'end') {
2004 date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
2006 this.activeDate.setFullYear(date);
2010 .directive('uibDatepicker', function() {
2013 templateUrl: function(element, attrs) {
2014 return attrs.templateUrl || 'template/datepicker/datepicker.html';
2017 datepickerMode: '=?',
2020 shortcutPropagation: '&?'
2022 require: ['uibDatepicker', '^ngModel'],
2023 controller: 'UibDatepickerController',
2024 controllerAs: 'datepicker',
2025 link: function(scope, element, attrs, ctrls) {
2026 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2028 datepickerCtrl.init(ngModelCtrl);
2033 .directive('uibDaypicker', function() {
2036 templateUrl: function(element, attrs) {
2037 return attrs.templateUrl || 'template/datepicker/day.html';
2039 require: ['^?uibDatepicker', 'uibDaypicker', '^?datepicker'],
2040 controller: 'UibDaypickerController',
2041 link: function(scope, element, attrs, ctrls) {
2042 var datepickerCtrl = ctrls[0] || ctrls[2],
2043 daypickerCtrl = ctrls[1];
2045 daypickerCtrl.init(datepickerCtrl);
2050 .directive('uibMonthpicker', function() {
2053 templateUrl: function(element, attrs) {
2054 return attrs.templateUrl || 'template/datepicker/month.html';
2056 require: ['^?uibDatepicker', 'uibMonthpicker', '^?datepicker'],
2057 controller: 'UibMonthpickerController',
2058 link: function(scope, element, attrs, ctrls) {
2059 var datepickerCtrl = ctrls[0] || ctrls[2],
2060 monthpickerCtrl = ctrls[1];
2062 monthpickerCtrl.init(datepickerCtrl);
2067 .directive('uibYearpicker', function() {
2070 templateUrl: function(element, attrs) {
2071 return attrs.templateUrl || 'template/datepicker/year.html';
2073 require: ['^?uibDatepicker', 'uibYearpicker', '^?datepicker'],
2074 controller: 'UibYearpickerController',
2075 link: function(scope, element, attrs, ctrls) {
2076 var ctrl = ctrls[0] || ctrls[2];
2077 angular.extend(ctrl, ctrls[1]);
2078 ctrl.yearpickerInit();
2085 .constant('uibDatepickerPopupConfig', {
2086 datepickerPopup: 'yyyy-MM-dd',
2087 datepickerPopupTemplateUrl: 'template/datepicker/popup.html',
2088 datepickerTemplateUrl: 'template/datepicker/datepicker.html',
2091 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
2094 currentText: 'Today',
2097 closeOnDateSelection: true,
2098 appendToBody: false,
2099 showButtonBar: true,
2103 .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout',
2104 function(scope, element, attrs, $compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
2107 isHtml5DateInput = false;
2108 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
2109 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl,
2112 scope.watchData = {};
2114 this.init = function(_ngModel_) {
2115 ngModel = _ngModel_;
2116 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
2117 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
2118 onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
2119 datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl;
2120 datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
2122 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
2124 if (datepickerPopupConfig.html5Types[attrs.type]) {
2125 dateFormat = datepickerPopupConfig.html5Types[attrs.type];
2126 isHtml5DateInput = true;
2128 dateFormat = attrs.datepickerPopup || attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
2129 attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
2130 var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
2131 // Invalidate the $modelValue to ensure that formatters re-run
2132 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
2133 if (newDateFormat !== dateFormat) {
2134 dateFormat = newDateFormat;
2135 ngModel.$modelValue = null;
2138 throw new Error('uibDatepickerPopup must have a date format specified.');
2145 throw new Error('uibDatepickerPopup must have a date format specified.');
2148 if (isHtml5DateInput && attrs.datepickerPopup) {
2149 throw new Error('HTML5 date input types do not support custom formats.');
2152 // popup element used to display calendar
2153 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
2156 'ng-change': 'dateSelection(date)',
2157 'template-url': datepickerPopupTemplateUrl
2160 // datepicker element
2161 datepickerEl = angular.element(popupEl.children()[0]);
2162 datepickerEl.attr('template-url', datepickerTemplateUrl);
2164 if (isHtml5DateInput) {
2165 if (attrs.type === 'month') {
2166 datepickerEl.attr('datepicker-mode', '"month"');
2167 datepickerEl.attr('min-mode', 'month');
2171 if (attrs.datepickerOptions) {
2172 var options = scope.$parent.$eval(attrs.datepickerOptions);
2173 if (options && options.initDate) {
2174 scope.initDate = options.initDate;
2175 datepickerEl.attr('init-date', 'initDate');
2176 delete options.initDate;
2178 angular.forEach(options, function(value, option) {
2179 datepickerEl.attr(cameltoDash(option), value);
2183 angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
2185 var getAttribute = $parse(attrs[key]);
2186 scope.$parent.$watch(getAttribute, function(value) {
2187 scope.watchData[key] = value;
2188 if (key === 'minDate' || key === 'maxDate') {
2189 cache[key] = new Date(value);
2192 datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
2194 // Propagate changes from datepicker to outside
2195 if (key === 'datepickerMode') {
2196 var setAttribute = getAttribute.assign;
2197 scope.$watch('watchData.' + key, function(value, oldvalue) {
2198 if (angular.isFunction(setAttribute) && value !== oldvalue) {
2199 setAttribute(scope.$parent, value);
2205 if (attrs.dateDisabled) {
2206 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
2209 if (attrs.showWeeks) {
2210 datepickerEl.attr('show-weeks', attrs.showWeeks);
2213 if (attrs.customClass) {
2214 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
2217 if (!isHtml5DateInput) {
2218 // Internal API to maintain the correct ng-invalid-[key] class
2219 ngModel.$$parserName = 'date';
2220 ngModel.$validators.date = validator;
2221 ngModel.$parsers.unshift(parseDate);
2222 ngModel.$formatters.push(function(value) {
2224 return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
2227 ngModel.$formatters.push(function(value) {
2233 // Detect changes in the view from the text box
2234 ngModel.$viewChangeListeners.push(function() {
2235 scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date);
2238 element.bind('keydown', inputKeydownBind);
2240 $popup = $compile(popupEl)(scope);
2241 // Prevent jQuery cache memory leak (template is now redundant after linking)
2245 $document.find('body').append($popup);
2247 element.after($popup);
2250 scope.$on('$destroy', function() {
2251 if (scope.isOpen === true) {
2252 if (!$rootScope.$$phase) {
2253 scope.$apply(function() {
2254 scope.isOpen = false;
2260 element.unbind('keydown', inputKeydownBind);
2261 $document.unbind('click', documentClickBind);
2265 scope.getText = function(key) {
2266 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
2269 scope.isDisabled = function(date) {
2270 if (date === 'today') {
2274 return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) ||
2275 (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0));
2278 scope.compare = function(date1, date2) {
2279 return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
2283 scope.dateSelection = function(dt) {
2284 if (angular.isDefined(dt)) {
2287 var date = scope.date ? dateFilter(scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
2289 ngModel.$setViewValue(date);
2291 if (closeOnDateSelection) {
2292 scope.isOpen = false;
2297 scope.keydown = function(evt) {
2298 if (evt.which === 27) {
2299 scope.isOpen = false;
2304 scope.select = function(date) {
2305 if (date === 'today') {
2306 var today = new Date();
2307 if (angular.isDate(scope.date)) {
2308 date = new Date(scope.date);
2309 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
2311 date = new Date(today.setHours(0, 0, 0, 0));
2314 scope.dateSelection(date);
2317 scope.close = function() {
2318 scope.isOpen = false;
2322 scope.$watch('isOpen', function(value) {
2324 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
2325 scope.position.top = scope.position.top + element.prop('offsetHeight');
2327 $timeout(function() {
2329 scope.$broadcast('uib:datepicker.focus');
2331 $document.bind('click', documentClickBind);
2334 $document.unbind('click', documentClickBind);
2338 function cameltoDash(string) {
2339 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
2342 function parseDate(viewValue) {
2343 if (angular.isNumber(viewValue)) {
2344 // presumably timestamp to date object
2345 viewValue = new Date(viewValue);
2350 } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
2352 } else if (angular.isString(viewValue)) {
2353 var date = dateParser.parse(viewValue, dateFormat, scope.date);
2364 function validator(modelValue, viewValue) {
2365 var value = modelValue || viewValue;
2367 if (!attrs.ngRequired && !value) {
2371 if (angular.isNumber(value)) {
2372 value = new Date(value);
2376 } else if (angular.isDate(value) && !isNaN(value)) {
2378 } else if (angular.isString(value)) {
2379 var date = dateParser.parse(value, dateFormat);
2380 return !isNaN(date);
2386 function documentClickBind(event) {
2387 var popup = $popup[0];
2388 var dpContainsTarget = element[0].contains(event.target);
2389 // The popup node may not be an element node
2390 // In some browsers (IE) only element nodes have the 'contains' function
2391 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
2392 if (scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
2393 scope.$apply(function() {
2394 scope.isOpen = false;
2399 function inputKeydownBind(evt) {
2400 if (evt.which === 27 && scope.isOpen) {
2401 evt.preventDefault();
2402 evt.stopPropagation();
2403 scope.$apply(function() {
2404 scope.isOpen = false;
2407 } else if (evt.which === 40 && !scope.isOpen) {
2408 evt.preventDefault();
2409 evt.stopPropagation();
2410 scope.$apply(function() {
2411 scope.isOpen = true;
2417 .directive('uibDatepickerPopup', function() {
2419 require: ['ngModel', 'uibDatepickerPopup'],
2420 controller: 'UibDatepickerPopupController',
2429 link: function(scope, element, attrs, ctrls) {
2430 var ngModel = ctrls[0],
2438 .directive('uibDatepickerPopupWrap', function() {
2442 templateUrl: function(element, attrs) {
2443 return attrs.templateUrl || 'template/datepicker/popup.html';
2448 /* Deprecated datepicker below */
2450 angular.module('ui.bootstrap.datepicker')
2452 .value('$datepickerSuppressWarning', false)
2454 .controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerSuppressError', '$datepickerSuppressWarning', function($scope, $attrs, $parse, $interpolate, $log, dateFilter, datepickerConfig, $datepickerSuppressError, $datepickerSuppressWarning) {
2455 if (!$datepickerSuppressWarning) {
2456 $log.warn('DatepickerController is now deprecated. Use UibDatepickerController instead.');
2460 ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
2462 this.modes = ['day', 'month', 'year'];
2464 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
2465 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
2466 self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
2469 angular.forEach(['minDate', 'maxDate'], function(key) {
2471 $scope.$parent.$watch($parse($attrs[key]), function(value) {
2472 self[key] = value ? new Date(value) : null;
2476 self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
2480 angular.forEach(['minMode', 'maxMode'], function(key) {
2482 $scope.$parent.$watch($parse($attrs[key]), function(value) {
2483 self[key] = angular.isDefined(value) ? value : $attrs[key];
2484 $scope[key] = self[key];
2485 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]))) {
2486 $scope.datepickerMode = self[key];
2490 self[key] = datepickerConfig[key] || null;
2491 $scope[key] = self[key];
2495 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
2496 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
2498 if (angular.isDefined($attrs.initDate)) {
2499 this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
2500 $scope.$parent.$watch($attrs.initDate, function(initDate) {
2501 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
2502 self.activeDate = initDate;
2507 this.activeDate = new Date();
2510 $scope.isActive = function(dateObject) {
2511 if (self.compare(dateObject.date, self.activeDate) === 0) {
2512 $scope.activeDateId = dateObject.uid;
2518 this.init = function(ngModelCtrl_) {
2519 ngModelCtrl = ngModelCtrl_;
2521 ngModelCtrl.$render = function() {
2526 this.render = function() {
2527 if (ngModelCtrl.$viewValue) {
2528 var date = new Date(ngModelCtrl.$viewValue),
2529 isValid = !isNaN(date);
2532 this.activeDate = date;
2533 } else if (!$datepickerSuppressError) {
2534 $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.');
2540 this.refreshView = function() {
2542 this._refreshView();
2544 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
2545 ngModelCtrl.$setValidity('dateDisabled', !date || (this.element && !this.isDisabled(date)));
2549 this.createDateObject = function(date, format) {
2550 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
2553 label: dateFilter(date, format),
2554 selected: model && this.compare(date, model) === 0,
2555 disabled: this.isDisabled(date),
2556 current: this.compare(date, new Date()) === 0,
2557 customClass: this.customClass(date)
2561 this.isDisabled = function(date) {
2562 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})));
2565 this.customClass = function(date) {
2566 return $scope.customClass({date: date, mode: $scope.datepickerMode});
2569 // Split array into smaller arrays
2570 this.split = function(arr, size) {
2572 while (arr.length > 0) {
2573 arrays.push(arr.splice(0, size));
2578 this.fixTimeZone = function(date) {
2579 var hours = date.getHours();
2580 date.setHours(hours === 23 ? hours + 2 : 0);
2583 $scope.select = function(date) {
2584 if ($scope.datepickerMode === self.minMode) {
2585 var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
2586 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
2587 ngModelCtrl.$setViewValue(dt);
2588 ngModelCtrl.$render();
2590 self.activeDate = date;
2591 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
2595 $scope.move = function(direction) {
2596 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
2597 month = self.activeDate.getMonth() + direction * (self.step.months || 0);
2598 self.activeDate.setFullYear(year, month, 1);
2602 $scope.toggleMode = function(direction) {
2603 direction = direction || 1;
2605 if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
2609 $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
2613 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
2615 var focusElement = function() {
2616 self.element[0].focus();
2619 $scope.$on('uib:datepicker.focus', focusElement);
2621 $scope.keydown = function(evt) {
2622 var key = $scope.keys[evt.which];
2624 if (!key || evt.shiftKey || evt.altKey) {
2628 evt.preventDefault();
2629 if (!self.shortcutPropagation) {
2630 evt.stopPropagation();
2633 if (key === 'enter' || key === 'space') {
2634 if (self.isDisabled(self.activeDate)) {
2635 return; // do nothing
2637 $scope.select(self.activeDate);
2638 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
2639 $scope.toggleMode(key === 'up' ? 1 : -1);
2641 self.handleKeyDown(key, evt);
2647 .directive('datepicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2650 templateUrl: function(element, attrs) {
2651 return attrs.templateUrl || 'template/datepicker/datepicker.html';
2654 datepickerMode: '=?',
2657 shortcutPropagation: '&?'
2659 require: ['datepicker', '^ngModel'],
2660 controller: 'DatepickerController',
2661 controllerAs: 'datepicker',
2662 link: function(scope, element, attrs, ctrls) {
2663 if (!$datepickerSuppressWarning) {
2664 $log.warn('datepicker is now deprecated. Use uib-datepicker instead.');
2667 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2669 datepickerCtrl.init(ngModelCtrl);
2674 .directive('daypicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2677 templateUrl: 'template/datepicker/day.html',
2678 require: ['^datepicker', 'daypicker'],
2679 controller: 'UibDaypickerController',
2680 link: function(scope, element, attrs, ctrls) {
2681 if (!$datepickerSuppressWarning) {
2682 $log.warn('daypicker is now deprecated. Use uib-daypicker instead.');
2685 var datepickerCtrl = ctrls[0],
2686 daypickerCtrl = ctrls[1];
2688 daypickerCtrl.init(datepickerCtrl);
2693 .directive('monthpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2696 templateUrl: 'template/datepicker/month.html',
2697 require: ['^datepicker', 'monthpicker'],
2698 controller: 'UibMonthpickerController',
2699 link: function(scope, element, attrs, ctrls) {
2700 if (!$datepickerSuppressWarning) {
2701 $log.warn('monthpicker is now deprecated. Use uib-monthpicker instead.');
2704 var datepickerCtrl = ctrls[0],
2705 monthpickerCtrl = ctrls[1];
2707 monthpickerCtrl.init(datepickerCtrl);
2712 .directive('yearpicker', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2715 templateUrl: 'template/datepicker/year.html',
2716 require: ['^datepicker', 'yearpicker'],
2717 controller: 'UibYearpickerController',
2718 link: function(scope, element, attrs, ctrls) {
2719 if (!$datepickerSuppressWarning) {
2720 $log.warn('yearpicker is now deprecated. Use uib-yearpicker instead.');
2723 var ctrl = ctrls[0];
2724 angular.extend(ctrl, ctrls[1]);
2725 ctrl.yearpickerInit();
2732 .directive('datepickerPopup', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2734 require: ['ngModel', 'datepickerPopup'],
2735 controller: 'UibDatepickerPopupController',
2744 link: function(scope, element, attrs, ctrls) {
2745 if (!$datepickerSuppressWarning) {
2746 $log.warn('datepicker-popup is now deprecated. Use uib-datepicker-popup instead.');
2749 var ngModel = ctrls[0],
2757 .directive('datepickerPopupWrap', ['$log', '$datepickerSuppressWarning', function($log, $datepickerSuppressWarning) {
2761 templateUrl: function(element, attrs) {
2762 return attrs.templateUrl || 'template/datepicker/popup.html';
2765 if (!$datepickerSuppressWarning) {
2766 $log.warn('datepicker-popup-wrap is now deprecated. Use uib-datepicker-popup-wrap instead.');
2772 angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2774 .constant('uibDropdownConfig', {
2778 .service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
2779 var openScope = null;
2781 this.open = function(dropdownScope) {
2783 $document.bind('click', closeDropdown);
2784 $document.bind('keydown', keybindFilter);
2787 if (openScope && openScope !== dropdownScope) {
2788 openScope.isOpen = false;
2791 openScope = dropdownScope;
2794 this.close = function(dropdownScope) {
2795 if (openScope === dropdownScope) {
2797 $document.unbind('click', closeDropdown);
2798 $document.unbind('keydown', keybindFilter);
2802 var closeDropdown = function(evt) {
2803 // This method may still be called during the same mouse event that
2804 // unbound this event handler. So check openScope before proceeding.
2805 if (!openScope) { return; }
2807 if (evt && openScope.getAutoClose() === 'disabled') { return ; }
2809 var toggleElement = openScope.getToggleElement();
2810 if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
2814 var dropdownElement = openScope.getDropdownElement();
2815 if (evt && openScope.getAutoClose() === 'outsideClick' &&
2816 dropdownElement && dropdownElement[0].contains(evt.target)) {
2820 openScope.isOpen = false;
2822 if (!$rootScope.$$phase) {
2827 var keybindFilter = function(evt) {
2828 if (evt.which === 27) {
2829 openScope.focusToggleElement();
2831 } else if (openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen) {
2832 evt.preventDefault();
2833 evt.stopPropagation();
2834 openScope.focusDropdownEntry(evt.which);
2839 .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) {
2841 scope = $scope.$new(), // create a child scope so we are not polluting original one
2843 openClass = dropdownConfig.openClass,
2845 setIsOpen = angular.noop,
2846 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
2847 appendToBody = false,
2848 keynavEnabled =false,
2849 selectedOption = null;
2852 $element.addClass('dropdown');
2854 this.init = function() {
2855 if ($attrs.isOpen) {
2856 getIsOpen = $parse($attrs.isOpen);
2857 setIsOpen = getIsOpen.assign;
2859 $scope.$watch(getIsOpen, function(value) {
2860 scope.isOpen = !!value;
2864 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
2865 keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
2867 if (appendToBody && self.dropdownMenu) {
2868 $document.find('body').append(self.dropdownMenu);
2869 $element.on('$destroy', function handleDestroyEvent() {
2870 self.dropdownMenu.remove();
2875 this.toggle = function(open) {
2876 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
2879 // Allow other directives to watch status
2880 this.isOpen = function() {
2881 return scope.isOpen;
2884 scope.getToggleElement = function() {
2885 return self.toggleElement;
2888 scope.getAutoClose = function() {
2889 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
2892 scope.getElement = function() {
2896 scope.isKeynavEnabled = function() {
2897 return keynavEnabled;
2900 scope.focusDropdownEntry = function(keyCode) {
2901 var elems = self.dropdownMenu ? //If append to body is used.
2902 (angular.element(self.dropdownMenu).find('a')) :
2903 (angular.element($element).find('ul').eq(0).find('a'));
2907 if (!angular.isNumber(self.selectedOption)) {
2908 self.selectedOption = 0;
2910 self.selectedOption = (self.selectedOption === elems.length - 1 ?
2911 self.selectedOption :
2912 self.selectedOption + 1);
2917 if (!angular.isNumber(self.selectedOption)) {
2918 self.selectedOption = elems.length - 1;
2920 self.selectedOption = self.selectedOption === 0 ?
2921 0 : self.selectedOption - 1;
2926 elems[self.selectedOption].focus();
2929 scope.getDropdownElement = function() {
2930 return self.dropdownMenu;
2933 scope.focusToggleElement = function() {
2934 if (self.toggleElement) {
2935 self.toggleElement[0].focus();
2939 scope.$watch('isOpen', function(isOpen, wasOpen) {
2940 if (appendToBody && self.dropdownMenu) {
2941 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
2943 top: pos.top + 'px',
2944 display: isOpen ? 'block' : 'none'
2947 var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
2949 css.left = pos.left + 'px';
2953 css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
2956 self.dropdownMenu.css(css);
2959 $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
2960 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
2961 toggleInvoker($scope, { open: !!isOpen });
2966 if (self.dropdownMenuTemplateUrl) {
2967 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
2968 templateScope = scope.$new();
2969 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
2970 var newEl = dropdownElement;
2971 self.dropdownMenu.replaceWith(newEl);
2972 self.dropdownMenu = newEl;
2977 scope.focusToggleElement();
2978 uibDropdownService.open(scope);
2980 if (self.dropdownMenuTemplateUrl) {
2981 if (templateScope) {
2982 templateScope.$destroy();
2984 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
2985 self.dropdownMenu.replaceWith(newEl);
2986 self.dropdownMenu = newEl;
2989 uibDropdownService.close(scope);
2990 self.selectedOption = null;
2993 if (angular.isFunction(setIsOpen)) {
2994 setIsOpen($scope, isOpen);
2998 $scope.$on('$locationChangeSuccess', function() {
2999 if (scope.getAutoClose() !== 'disabled') {
3000 scope.isOpen = false;
3004 var offDestroy = $scope.$on('$destroy', function() {
3007 scope.$on('$destroy', offDestroy);
3010 .directive('uibDropdown', function() {
3012 controller: 'UibDropdownController',
3013 link: function(scope, element, attrs, dropdownCtrl) {
3014 dropdownCtrl.init();
3019 .directive('uibDropdownMenu', function() {
3022 require: '?^uibDropdown',
3023 link: function(scope, element, attrs, dropdownCtrl) {
3024 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
3028 element.addClass('dropdown-menu');
3030 var tplUrl = attrs.templateUrl;
3032 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
3035 if (!dropdownCtrl.dropdownMenu) {
3036 dropdownCtrl.dropdownMenu = element;
3042 .directive('uibKeyboardNav', function() {
3045 require: '?^uibDropdown',
3046 link: function(scope, element, attrs, dropdownCtrl) {
3047 element.bind('keydown', function(e) {
3048 if ([38, 40].indexOf(e.which) !== -1) {
3050 e.stopPropagation();
3052 var elems = dropdownCtrl.dropdownMenu.find('a');
3055 case (40): { // Down
3056 if (!angular.isNumber(dropdownCtrl.selectedOption)) {
3057 dropdownCtrl.selectedOption = 0;
3059 dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
3060 dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
3065 if (!angular.isNumber(dropdownCtrl.selectedOption)) {
3066 dropdownCtrl.selectedOption = elems.length - 1;
3068 dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
3069 0 : dropdownCtrl.selectedOption - 1;
3074 elems[dropdownCtrl.selectedOption].focus();
3081 .directive('uibDropdownToggle', function() {
3083 require: '?^uibDropdown',
3084 link: function(scope, element, attrs, dropdownCtrl) {
3085 if (!dropdownCtrl) {
3089 element.addClass('dropdown-toggle');
3091 dropdownCtrl.toggleElement = element;
3093 var toggleDropdown = function(event) {
3094 event.preventDefault();
3096 if (!element.hasClass('disabled') && !attrs.disabled) {
3097 scope.$apply(function() {
3098 dropdownCtrl.toggle();
3103 element.bind('click', toggleDropdown);
3106 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
3107 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
3108 element.attr('aria-expanded', !!isOpen);
3111 scope.$on('$destroy', function() {
3112 element.unbind('click', toggleDropdown);
3118 /* Deprecated dropdown below */
3120 angular.module('ui.bootstrap.dropdown')
3122 .value('$dropdownSuppressWarning', false)
3124 .service('dropdownService', ['$log', '$dropdownSuppressWarning', 'uibDropdownService', function($log, $dropdownSuppressWarning, uibDropdownService) {
3125 if (!$dropdownSuppressWarning) {
3126 $log.warn('dropdownService is now deprecated. Use uibDropdownService instead.');
3129 angular.extend(this, uibDropdownService);
3132 .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) {
3133 if (!$dropdownSuppressWarning) {
3134 $log.warn('DropdownController is now deprecated. Use UibDropdownController instead.');
3138 scope = $scope.$new(), // create a child scope so we are not polluting original one
3140 openClass = dropdownConfig.openClass,
3142 setIsOpen = angular.noop,
3143 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
3144 appendToBody = false,
3145 keynavEnabled =false,
3146 selectedOption = null;
3149 $element.addClass('dropdown');
3151 this.init = function() {
3152 if ($attrs.isOpen) {
3153 getIsOpen = $parse($attrs.isOpen);
3154 setIsOpen = getIsOpen.assign;
3156 $scope.$watch(getIsOpen, function(value) {
3157 scope.isOpen = !!value;
3161 appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
3162 keynavEnabled = angular.isDefined($attrs.uibKeyboardNav);
3164 if (appendToBody && self.dropdownMenu) {
3165 $document.find('body').append(self.dropdownMenu);
3166 $element.on('$destroy', function handleDestroyEvent() {
3167 self.dropdownMenu.remove();
3172 this.toggle = function(open) {
3173 return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
3176 // Allow other directives to watch status
3177 this.isOpen = function() {
3178 return scope.isOpen;
3181 scope.getToggleElement = function() {
3182 return self.toggleElement;
3185 scope.getAutoClose = function() {
3186 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
3189 scope.getElement = function() {
3193 scope.isKeynavEnabled = function() {
3194 return keynavEnabled;
3197 scope.focusDropdownEntry = function(keyCode) {
3198 var elems = self.dropdownMenu ? //If append to body is used.
3199 (angular.element(self.dropdownMenu).find('a')) :
3200 (angular.element($element).find('ul').eq(0).find('a'));
3204 if (!angular.isNumber(self.selectedOption)) {
3205 self.selectedOption = 0;
3207 self.selectedOption = (self.selectedOption === elems.length -1 ?
3208 self.selectedOption :
3209 self.selectedOption + 1);
3214 if (!angular.isNumber(self.selectedOption)) {
3215 self.selectedOption = elems.length - 1;
3217 self.selectedOption = self.selectedOption === 0 ?
3218 0 : self.selectedOption - 1;
3223 elems[self.selectedOption].focus();
3226 scope.getDropdownElement = function() {
3227 return self.dropdownMenu;
3230 scope.focusToggleElement = function() {
3231 if (self.toggleElement) {
3232 self.toggleElement[0].focus();
3236 scope.$watch('isOpen', function(isOpen, wasOpen) {
3237 if (appendToBody && self.dropdownMenu) {
3238 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true);
3240 top: pos.top + 'px',
3241 display: isOpen ? 'block' : 'none'
3244 var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
3246 css.left = pos.left + 'px';
3250 css.right = (window.innerWidth - (pos.left + $element.prop('offsetWidth'))) + 'px';
3253 self.dropdownMenu.css(css);
3256 $animate[isOpen ? 'addClass' : 'removeClass']($element, openClass).then(function() {
3257 if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
3258 toggleInvoker($scope, { open: !!isOpen });
3263 if (self.dropdownMenuTemplateUrl) {
3264 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
3265 templateScope = scope.$new();
3266 $compile(tplContent.trim())(templateScope, function(dropdownElement) {
3267 var newEl = dropdownElement;
3268 self.dropdownMenu.replaceWith(newEl);
3269 self.dropdownMenu = newEl;
3274 scope.focusToggleElement();
3275 uibDropdownService.open(scope);
3277 if (self.dropdownMenuTemplateUrl) {
3278 if (templateScope) {
3279 templateScope.$destroy();
3281 var newEl = angular.element('<ul class="dropdown-menu"></ul>');
3282 self.dropdownMenu.replaceWith(newEl);
3283 self.dropdownMenu = newEl;
3286 uibDropdownService.close(scope);
3287 self.selectedOption = null;
3290 if (angular.isFunction(setIsOpen)) {
3291 setIsOpen($scope, isOpen);
3295 $scope.$on('$locationChangeSuccess', function() {
3296 if (scope.getAutoClose() !== 'disabled') {
3297 scope.isOpen = false;
3301 var offDestroy = $scope.$on('$destroy', function() {
3304 scope.$on('$destroy', offDestroy);
3307 .directive('dropdown', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
3309 controller: 'DropdownController',
3310 link: function(scope, element, attrs, dropdownCtrl) {
3311 if (!$dropdownSuppressWarning) {
3312 $log.warn('dropdown is now deprecated. Use uib-dropdown instead.');
3315 dropdownCtrl.init();
3320 .directive('dropdownMenu', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
3323 require: '?^dropdown',
3324 link: function(scope, element, attrs, dropdownCtrl) {
3325 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
3329 if (!$dropdownSuppressWarning) {
3330 $log.warn('dropdown-menu is now deprecated. Use uib-dropdown-menu instead.');
3333 element.addClass('dropdown-menu');
3335 var tplUrl = attrs.templateUrl;
3337 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
3340 if (!dropdownCtrl.dropdownMenu) {
3341 dropdownCtrl.dropdownMenu = element;
3347 .directive('keyboardNav', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
3350 require: '?^dropdown',
3351 link: function(scope, element, attrs, dropdownCtrl) {
3352 if (!$dropdownSuppressWarning) {
3353 $log.warn('keyboard-nav is now deprecated. Use uib-keyboard-nav instead.');
3356 element.bind('keydown', function(e) {
3357 if ([38, 40].indexOf(e.which) !== -1) {
3359 e.stopPropagation();
3361 var elems = dropdownCtrl.dropdownMenu.find('a');
3364 case (40): { // Down
3365 if (!angular.isNumber(dropdownCtrl.selectedOption)) {
3366 dropdownCtrl.selectedOption = 0;
3368 dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
3369 dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
3374 if (!angular.isNumber(dropdownCtrl.selectedOption)) {
3375 dropdownCtrl.selectedOption = elems.length - 1;
3377 dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
3378 0 : dropdownCtrl.selectedOption - 1;
3383 elems[dropdownCtrl.selectedOption].focus();
3390 .directive('dropdownToggle', ['$log', '$dropdownSuppressWarning', function($log, $dropdownSuppressWarning) {
3392 require: '?^dropdown',
3393 link: function(scope, element, attrs, dropdownCtrl) {
3394 if (!$dropdownSuppressWarning) {
3395 $log.warn('dropdown-toggle is now deprecated. Use uib-dropdown-toggle instead.');
3398 if (!dropdownCtrl) {
3402 element.addClass('dropdown-toggle');
3404 dropdownCtrl.toggleElement = element;
3406 var toggleDropdown = function(event) {
3407 event.preventDefault();
3409 if (!element.hasClass('disabled') && !attrs.disabled) {
3410 scope.$apply(function() {
3411 dropdownCtrl.toggle();
3416 element.bind('click', toggleDropdown);
3419 element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
3420 scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
3421 element.attr('aria-expanded', !!isOpen);
3424 scope.$on('$destroy', function() {
3425 element.unbind('click', toggleDropdown);
3431 angular.module('ui.bootstrap.stackedMap', [])
3433 * A helper, internal data structure that acts as a map but also allows getting / removing
3434 * elements in the LIFO order
3436 .factory('$$stackedMap', function() {
3438 createNew: function() {
3442 add: function(key, value) {
3448 get: function(key) {
3449 for (var i = 0; i < stack.length; i++) {
3450 if (key == stack[i].key) {
3457 for (var i = 0; i < stack.length; i++) {
3458 keys.push(stack[i].key);
3463 return stack[stack.length - 1];
3465 remove: function(key) {
3467 for (var i = 0; i < stack.length; i++) {
3468 if (key == stack[i].key) {
3473 return stack.splice(idx, 1)[0];
3475 removeTop: function() {
3476 return stack.splice(stack.length - 1, 1)[0];
3478 length: function() {
3479 return stack.length;
3485 angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap'])
3487 * A helper, internal data structure that stores all references attached to key
3489 .factory('$$multiMap', function() {
3491 createNew: function() {
3495 entries: function() {
3496 return Object.keys(map).map(function(key) {
3503 get: function(key) {
3506 hasKey: function(key) {
3510 return Object.keys(map);
3512 put: function(key, value) {
3517 map[key].push(value);
3519 remove: function(key, value) {
3520 var values = map[key];
3526 var idx = values.indexOf(value);
3529 values.splice(idx, 1);
3532 if (!values.length) {
3542 * A helper directive for the $modal service. It creates a backdrop element.
3544 .directive('uibModalBackdrop', [
3545 '$animate', '$injector', '$uibModalStack',
3546 function($animate , $injector, $modalStack) {
3547 var $animateCss = null;
3549 if ($injector.has('$animateCss')) {
3550 $animateCss = $injector.get('$animateCss');
3555 templateUrl: 'template/modal/backdrop.html',
3556 compile: function(tElement, tAttrs) {
3557 tElement.addClass(tAttrs.backdropClass);
3562 function linkFn(scope, element, attrs) {
3563 // Temporary fix for prefixing
3564 element.addClass('modal-backdrop');
3566 if (attrs.modalInClass) {
3568 $animateCss(element, {
3569 addClass: attrs.modalInClass
3572 $animate.addClass(element, attrs.modalInClass);
3575 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3576 var done = setIsAsync();
3578 $animateCss(element, {
3579 removeClass: attrs.modalInClass
3580 }).start().then(done);
3582 $animate.removeClass(element, attrs.modalInClass).then(done);
3589 .directive('uibModalWindow', [
3590 '$uibModalStack', '$q', '$animate', '$injector',
3591 function($modalStack , $q , $animate, $injector) {
3592 var $animateCss = null;
3594 if ($injector.has('$animateCss')) {
3595 $animateCss = $injector.get('$animateCss');
3604 templateUrl: function(tElement, tAttrs) {
3605 return tAttrs.templateUrl || 'template/modal/window.html';
3607 link: function(scope, element, attrs) {
3608 element.addClass(attrs.windowClass || '');
3609 element.addClass(attrs.windowTopClass || '');
3610 scope.size = attrs.size;
3612 scope.close = function(evt) {
3613 var modal = $modalStack.getTop();
3614 if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
3615 evt.preventDefault();
3616 evt.stopPropagation();
3617 $modalStack.dismiss(modal.key, 'backdrop click');
3621 // moved from template to fix issue #2280
3622 element.on('click', scope.close);
3624 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
3625 // We can detect that by using this property in the template associated with this directive and then use
3626 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
3627 scope.$isRendered = true;
3629 // Deferred object that will be resolved when this modal is render.
3630 var modalRenderDeferObj = $q.defer();
3631 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
3632 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
3633 attrs.$observe('modalRender', function(value) {
3634 if (value == 'true') {
3635 modalRenderDeferObj.resolve();
3639 modalRenderDeferObj.promise.then(function() {
3640 var animationPromise = null;
3642 if (attrs.modalInClass) {
3644 animationPromise = $animateCss(element, {
3645 addClass: attrs.modalInClass
3648 animationPromise = $animate.addClass(element, attrs.modalInClass);
3651 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3652 var done = setIsAsync();
3654 $animateCss(element, {
3655 removeClass: attrs.modalInClass
3656 }).start().then(done);
3658 $animate.removeClass(element, attrs.modalInClass).then(done);
3664 $q.when(animationPromise).then(function() {
3665 var inputWithAutofocus = element[0].querySelector('[autofocus]');
3667 * Auto-focusing of a freshly-opened modal element causes any child elements
3668 * with the autofocus attribute to lose focus. This is an issue on touch
3669 * based devices which will show and then hide the onscreen keyboard.
3670 * Attempts to refocus the autofocus element via JavaScript will not reopen
3671 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
3672 * the modal element if the modal does not contain an autofocus element.
3674 if (inputWithAutofocus) {
3675 inputWithAutofocus.focus();
3681 // Notify {@link $modalStack} that modal is rendered.
3682 var modal = $modalStack.getTop();
3684 $modalStack.modalRendered(modal.key);
3691 .directive('uibModalAnimationClass', function() {
3693 compile: function(tElement, tAttrs) {
3694 if (tAttrs.modalAnimation) {
3695 tElement.addClass(tAttrs.uibModalAnimationClass);
3701 .directive('uibModalTransclude', function() {
3703 link: function($scope, $element, $attrs, controller, $transclude) {
3704 $transclude($scope.$parent, function(clone) {
3706 $element.append(clone);
3712 .factory('$uibModalStack', [
3713 '$animate', '$timeout', '$document', '$compile', '$rootScope',
3718 function($animate , $timeout , $document , $compile , $rootScope ,
3723 var $animateCss = null;
3725 if ($injector.has('$animateCss')) {
3726 $animateCss = $injector.get('$animateCss');
3729 var OPENED_MODAL_CLASS = 'modal-open';
3731 var backdropDomEl, backdropScope;
3732 var openedWindows = $$stackedMap.createNew();
3733 var openedClasses = $$multiMap.createNew();
3735 NOW_CLOSING_EVENT: 'modal.stack.now-closing'
3738 //Modal focus behavior
3739 var focusableElementList;
3741 var tababbleSelector = 'a[href], area[href], input:not([disabled]), ' +
3742 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
3743 'iframe, object, embed, *[tabindex], *[contenteditable=true]';
3745 function backdropIndex() {
3746 var topBackdropIndex = -1;
3747 var opened = openedWindows.keys();
3748 for (var i = 0; i < opened.length; i++) {
3749 if (openedWindows.get(opened[i]).value.backdrop) {
3750 topBackdropIndex = i;
3753 return topBackdropIndex;
3756 $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
3757 if (backdropScope) {
3758 backdropScope.index = newBackdropIndex;
3762 function removeModalWindow(modalInstance, elementToReceiveFocus) {
3763 var body = $document.find('body').eq(0);
3764 var modalWindow = openedWindows.get(modalInstance).value;
3766 //clean up the stack
3767 openedWindows.remove(modalInstance);
3769 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
3770 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
3771 openedClasses.remove(modalBodyClass, modalInstance);
3772 body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
3773 toggleTopWindowClass(true);
3775 checkRemoveBackdrop();
3777 //move focus to specified element if available, or else to body
3778 if (elementToReceiveFocus && elementToReceiveFocus.focus) {
3779 elementToReceiveFocus.focus();
3785 // Add or remove "windowTopClass" from the top window in the stack
3786 function toggleTopWindowClass(toggleSwitch) {
3789 if (openedWindows.length() > 0) {
3790 modalWindow = openedWindows.top().value;
3791 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
3795 function checkRemoveBackdrop() {
3796 //remove backdrop if no longer needed
3797 if (backdropDomEl && backdropIndex() == -1) {
3798 var backdropScopeRef = backdropScope;
3799 removeAfterAnimate(backdropDomEl, backdropScope, function() {
3800 backdropScopeRef = null;
3802 backdropDomEl = undefined;
3803 backdropScope = undefined;
3807 function removeAfterAnimate(domEl, scope, done) {
3809 var asyncPromise = null;
3810 var setIsAsync = function() {
3811 if (!asyncDeferred) {
3812 asyncDeferred = $q.defer();
3813 asyncPromise = asyncDeferred.promise;
3816 return function asyncDone() {
3817 asyncDeferred.resolve();
3820 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
3822 // Note that it's intentional that asyncPromise might be null.
3823 // That's when setIsAsync has not been called during the
3824 // NOW_CLOSING_EVENT broadcast.
3825 return $q.when(asyncPromise).then(afterAnimating);
3827 function afterAnimating() {
3828 if (afterAnimating.done) {
3831 afterAnimating.done = true;
3834 $animateCss(domEl, {
3836 }).start().then(function() {
3840 $animate.leave(domEl);
3849 $document.bind('keydown', function(evt) {
3850 if (evt.isDefaultPrevented()) {
3854 var modal = openedWindows.top();
3855 if (modal && modal.value.keyboard) {
3856 switch (evt.which) {
3858 evt.preventDefault();
3859 $rootScope.$apply(function() {
3860 $modalStack.dismiss(modal.key, 'escape key press');
3865 $modalStack.loadFocusElementList(modal);
3866 var focusChanged = false;
3868 if ($modalStack.isFocusInFirstItem(evt)) {
3869 focusChanged = $modalStack.focusLastFocusableElement();
3872 if ($modalStack.isFocusInLastItem(evt)) {
3873 focusChanged = $modalStack.focusFirstFocusableElement();
3878 evt.preventDefault();
3879 evt.stopPropagation();
3887 $modalStack.open = function(modalInstance, modal) {
3888 var modalOpener = $document[0].activeElement,
3889 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
3891 toggleTopWindowClass(false);
3893 openedWindows.add(modalInstance, {
3894 deferred: modal.deferred,
3895 renderDeferred: modal.renderDeferred,
3896 modalScope: modal.scope,
3897 backdrop: modal.backdrop,
3898 keyboard: modal.keyboard,
3899 openedClass: modal.openedClass,
3900 windowTopClass: modal.windowTopClass
3903 openedClasses.put(modalBodyClass, modalInstance);
3905 var body = $document.find('body').eq(0),
3906 currBackdropIndex = backdropIndex();
3908 if (currBackdropIndex >= 0 && !backdropDomEl) {
3909 backdropScope = $rootScope.$new(true);
3910 backdropScope.index = currBackdropIndex;
3911 var angularBackgroundDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
3912 angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
3913 if (modal.animation) {
3914 angularBackgroundDomEl.attr('modal-animation', 'true');
3916 backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
3917 body.append(backdropDomEl);
3920 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
3922 'template-url': modal.windowTemplateUrl,
3923 'window-class': modal.windowClass,
3924 'window-top-class': modal.windowTopClass,
3926 'index': openedWindows.length() - 1,
3927 'animate': 'animate'
3928 }).html(modal.content);
3929 if (modal.animation) {
3930 angularDomEl.attr('modal-animation', 'true');
3933 var modalDomEl = $compile(angularDomEl)(modal.scope);
3934 openedWindows.top().value.modalDomEl = modalDomEl;
3935 openedWindows.top().value.modalOpener = modalOpener;
3936 body.append(modalDomEl);
3937 body.addClass(modalBodyClass);
3939 $modalStack.clearFocusListCache();
3942 function broadcastClosing(modalWindow, resultOrReason, closing) {
3943 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
3946 $modalStack.close = function(modalInstance, result) {
3947 var modalWindow = openedWindows.get(modalInstance);
3948 if (modalWindow && broadcastClosing(modalWindow, result, true)) {
3949 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
3950 modalWindow.value.deferred.resolve(result);
3951 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
3954 return !modalWindow;
3957 $modalStack.dismiss = function(modalInstance, reason) {
3958 var modalWindow = openedWindows.get(modalInstance);
3959 if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
3960 modalWindow.value.modalScope.$$uibDestructionScheduled = true;
3961 modalWindow.value.deferred.reject(reason);
3962 removeModalWindow(modalInstance, modalWindow.value.modalOpener);
3965 return !modalWindow;
3968 $modalStack.dismissAll = function(reason) {
3969 var topModal = this.getTop();
3970 while (topModal && this.dismiss(topModal.key, reason)) {
3971 topModal = this.getTop();
3975 $modalStack.getTop = function() {
3976 return openedWindows.top();
3979 $modalStack.modalRendered = function(modalInstance) {
3980 var modalWindow = openedWindows.get(modalInstance);
3982 modalWindow.value.renderDeferred.resolve();
3986 $modalStack.focusFirstFocusableElement = function() {
3987 if (focusableElementList.length > 0) {
3988 focusableElementList[0].focus();
3993 $modalStack.focusLastFocusableElement = function() {
3994 if (focusableElementList.length > 0) {
3995 focusableElementList[focusableElementList.length - 1].focus();
4001 $modalStack.isFocusInFirstItem = function(evt) {
4002 if (focusableElementList.length > 0) {
4003 return (evt.target || evt.srcElement) == focusableElementList[0];
4008 $modalStack.isFocusInLastItem = function(evt) {
4009 if (focusableElementList.length > 0) {
4010 return (evt.target || evt.srcElement) == focusableElementList[focusableElementList.length - 1];
4015 $modalStack.clearFocusListCache = function() {
4016 focusableElementList = [];
4020 $modalStack.loadFocusElementList = function(modalWindow) {
4021 if (focusableElementList === undefined || !focusableElementList.length) {
4023 var modalDomE1 = modalWindow.value.modalDomEl;
4024 if (modalDomE1 && modalDomE1.length) {
4025 focusableElementList = modalDomE1[0].querySelectorAll(tababbleSelector);
4034 .provider('$uibModal', function() {
4035 var $modalProvider = {
4038 backdrop: true, //can also be false or 'static'
4041 $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$uibModalStack', '$modalSuppressWarning', '$log',
4042 function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack, $modalSuppressWarning, $log) {
4045 function getTemplatePromise(options) {
4046 return options.template ? $q.when(options.template) :
4047 $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl);
4050 function getResolvePromises(resolves) {
4051 var promisesArr = [];
4052 angular.forEach(resolves, function(value) {
4053 if (angular.isFunction(value) || angular.isArray(value)) {
4054 promisesArr.push($q.when($injector.invoke(value)));
4055 } else if (angular.isString(value)) {
4056 promisesArr.push($q.when($injector.get(value)));
4058 promisesArr.push($q.when(value));
4064 var promiseChain = null;
4065 $modal.getPromiseChain = function() {
4066 return promiseChain;
4069 $modal.open = function(modalOptions) {
4070 var modalResultDeferred = $q.defer();
4071 var modalOpenedDeferred = $q.defer();
4072 var modalRenderDeferred = $q.defer();
4074 //prepare an instance of a modal to be injected into controllers and returned to a caller
4075 var modalInstance = {
4076 result: modalResultDeferred.promise,
4077 opened: modalOpenedDeferred.promise,
4078 rendered: modalRenderDeferred.promise,
4079 close: function (result) {
4080 return $modalStack.close(modalInstance, result);
4082 dismiss: function (reason) {
4083 return $modalStack.dismiss(modalInstance, reason);
4087 //merge and clean up options
4088 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
4089 modalOptions.resolve = modalOptions.resolve || {};
4092 if (!modalOptions.template && !modalOptions.templateUrl) {
4093 throw new Error('One of template or templateUrl options is required.');
4096 var templateAndResolvePromise =
4097 $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
4099 function resolveWithTemplate() {
4100 return templateAndResolvePromise;
4103 // Wait for the resolution of the existing promise chain.
4104 // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
4105 // Then add to $modalStack and resolve opened.
4106 // Finally clean up the chain variable if no subsequent modal has overwritten it.
4108 samePromise = promiseChain = $q.all([promiseChain])
4109 .then(resolveWithTemplate, resolveWithTemplate)
4110 .then(function resolveSuccess(tplAndVars) {
4112 var modalScope = (modalOptions.scope || $rootScope).$new();
4113 modalScope.$close = modalInstance.close;
4114 modalScope.$dismiss = modalInstance.dismiss;
4116 modalScope.$on('$destroy', function() {
4117 if (!modalScope.$$uibDestructionScheduled) {
4118 modalScope.$dismiss('$uibUnscheduledDestruction');
4122 var ctrlInstance, ctrlLocals = {};
4123 var resolveIter = 1;
4126 if (modalOptions.controller) {
4127 ctrlLocals.$scope = modalScope;
4128 ctrlLocals.$uibModalInstance = modalInstance;
4129 Object.defineProperty(ctrlLocals, '$modalInstance', {
4131 if (!$modalSuppressWarning) {
4132 $log.warn('$modalInstance is now deprecated. Use $uibModalInstance instead.');
4135 return modalInstance;
4138 angular.forEach(modalOptions.resolve, function(value, key) {
4139 ctrlLocals[key] = tplAndVars[resolveIter++];
4142 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
4143 if (modalOptions.controllerAs) {
4144 if (modalOptions.bindToController) {
4145 angular.extend(ctrlInstance, modalScope);
4148 modalScope[modalOptions.controllerAs] = ctrlInstance;
4152 $modalStack.open(modalInstance, {
4154 deferred: modalResultDeferred,
4155 renderDeferred: modalRenderDeferred,
4156 content: tplAndVars[0],
4157 animation: modalOptions.animation,
4158 backdrop: modalOptions.backdrop,
4159 keyboard: modalOptions.keyboard,
4160 backdropClass: modalOptions.backdropClass,
4161 windowTopClass: modalOptions.windowTopClass,
4162 windowClass: modalOptions.windowClass,
4163 windowTemplateUrl: modalOptions.windowTemplateUrl,
4164 size: modalOptions.size,
4165 openedClass: modalOptions.openedClass
4167 modalOpenedDeferred.resolve(true);
4169 }, function resolveError(reason) {
4170 modalOpenedDeferred.reject(reason);
4171 modalResultDeferred.reject(reason);
4173 .finally(function() {
4174 if (promiseChain === samePromise) {
4175 promiseChain = null;
4179 return modalInstance;
4187 return $modalProvider;
4190 /* deprecated modal below */
4192 angular.module('ui.bootstrap.modal')
4194 .value('$modalSuppressWarning', false)
4197 * A helper directive for the $modal service. It creates a backdrop element.
4199 .directive('modalBackdrop', [
4200 '$animate', '$injector', '$modalStack', '$log', '$modalSuppressWarning',
4201 function($animate , $injector, $modalStack, $log, $modalSuppressWarning) {
4202 var $animateCss = null;
4204 if ($injector.has('$animateCss')) {
4205 $animateCss = $injector.get('$animateCss');
4210 templateUrl: 'template/modal/backdrop.html',
4211 compile: function(tElement, tAttrs) {
4212 tElement.addClass(tAttrs.backdropClass);
4217 function linkFn(scope, element, attrs) {
4218 if (!$modalSuppressWarning) {
4219 $log.warn('modal-backdrop is now deprecated. Use uib-modal-backdrop instead.');
4221 element.addClass('modal-backdrop');
4223 if (attrs.modalInClass) {
4225 $animateCss(element, {
4226 addClass: attrs.modalInClass
4229 $animate.addClass(element, attrs.modalInClass);
4232 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
4233 var done = setIsAsync();
4235 $animateCss(element, {
4236 removeClass: attrs.modalInClass
4237 }).start().then(done);
4239 $animate.removeClass(element, attrs.modalInClass).then(done);
4246 .directive('modalWindow', [
4247 '$modalStack', '$q', '$animate', '$injector', '$log', '$modalSuppressWarning',
4248 function($modalStack , $q , $animate, $injector, $log, $modalSuppressWarning) {
4249 var $animateCss = null;
4251 if ($injector.has('$animateCss')) {
4252 $animateCss = $injector.get('$animateCss');
4261 templateUrl: function(tElement, tAttrs) {
4262 return tAttrs.templateUrl || 'template/modal/window.html';
4264 link: function(scope, element, attrs) {
4265 if (!$modalSuppressWarning) {
4266 $log.warn('modal-window is now deprecated. Use uib-modal-window instead.');
4268 element.addClass(attrs.windowClass || '');
4269 element.addClass(attrs.windowTopClass || '');
4270 scope.size = attrs.size;
4272 scope.close = function(evt) {
4273 var modal = $modalStack.getTop();
4274 if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
4275 evt.preventDefault();
4276 evt.stopPropagation();
4277 $modalStack.dismiss(modal.key, 'backdrop click');
4281 // moved from template to fix issue #2280
4282 element.on('click', scope.close);
4284 // This property is only added to the scope for the purpose of detecting when this directive is rendered.
4285 // We can detect that by using this property in the template associated with this directive and then use
4286 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
4287 scope.$isRendered = true;
4289 // Deferred object that will be resolved when this modal is render.
4290 var modalRenderDeferObj = $q.defer();
4291 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
4292 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
4293 attrs.$observe('modalRender', function(value) {
4294 if (value == 'true') {
4295 modalRenderDeferObj.resolve();
4299 modalRenderDeferObj.promise.then(function() {
4300 var animationPromise = null;
4302 if (attrs.modalInClass) {
4304 animationPromise = $animateCss(element, {
4305 addClass: attrs.modalInClass
4308 animationPromise = $animate.addClass(element, attrs.modalInClass);
4311 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
4312 var done = setIsAsync();
4314 $animateCss(element, {
4315 removeClass: attrs.modalInClass
4316 }).start().then(done);
4318 $animate.removeClass(element, attrs.modalInClass).then(done);
4324 $q.when(animationPromise).then(function() {
4325 var inputWithAutofocus = element[0].querySelector('[autofocus]');
4327 * Auto-focusing of a freshly-opened modal element causes any child elements
4328 * with the autofocus attribute to lose focus. This is an issue on touch
4329 * based devices which will show and then hide the onscreen keyboard.
4330 * Attempts to refocus the autofocus element via JavaScript will not reopen
4331 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
4332 * the modal element if the modal does not contain an autofocus element.
4334 if (inputWithAutofocus) {
4335 inputWithAutofocus.focus();
4341 // Notify {@link $modalStack} that modal is rendered.
4342 var modal = $modalStack.getTop();
4344 $modalStack.modalRendered(modal.key);
4351 .directive('modalAnimationClass', [
4352 '$log', '$modalSuppressWarning',
4353 function ($log, $modalSuppressWarning) {
4355 compile: function(tElement, tAttrs) {
4356 if (!$modalSuppressWarning) {
4357 $log.warn('modal-animation-class is now deprecated. Use uib-modal-animation-class instead.');
4359 if (tAttrs.modalAnimation) {
4360 tElement.addClass(tAttrs.modalAnimationClass);
4366 .directive('modalTransclude', [
4367 '$log', '$modalSuppressWarning',
4368 function ($log, $modalSuppressWarning) {
4370 link: function($scope, $element, $attrs, controller, $transclude) {
4371 if (!$modalSuppressWarning) {
4372 $log.warn('modal-transclude is now deprecated. Use uib-modal-transclude instead.');
4374 $transclude($scope.$parent, function(clone) {
4376 $element.append(clone);
4382 .service('$modalStack', [
4383 '$animate', '$timeout', '$document', '$compile', '$rootScope',
4390 '$modalSuppressWarning',
4391 function($animate , $timeout , $document , $compile , $rootScope ,
4398 $modalSuppressWarning) {
4399 if (!$modalSuppressWarning) {
4400 $log.warn('$modalStack is now deprecated. Use $uibModalStack instead.');
4403 angular.extend(this, $uibModalStack);
4406 .provider('$modal', ['$uibModalProvider', function($uibModalProvider) {
4407 angular.extend(this, $uibModalProvider);
4409 this.$get = ['$injector', '$log', '$modalSuppressWarning',
4410 function ($injector, $log, $modalSuppressWarning) {
4411 if (!$modalSuppressWarning) {
4412 $log.warn('$modal is now deprecated. Use $uibModal instead.');
4415 return $injector.invoke($uibModalProvider.$get);
4419 angular.module('ui.bootstrap.pagination', [])
4420 .controller('UibPaginationController', ['$scope', '$attrs', '$parse', function($scope, $attrs, $parse) {
4422 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
4423 setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
4425 this.init = function(ngModelCtrl_, config) {
4426 ngModelCtrl = ngModelCtrl_;
4427 this.config = config;
4429 ngModelCtrl.$render = function() {
4433 if ($attrs.itemsPerPage) {
4434 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
4435 self.itemsPerPage = parseInt(value, 10);
4436 $scope.totalPages = self.calculateTotalPages();
4439 this.itemsPerPage = config.itemsPerPage;
4442 $scope.$watch('totalItems', function() {
4443 $scope.totalPages = self.calculateTotalPages();
4446 $scope.$watch('totalPages', function(value) {
4447 setNumPages($scope.$parent, value); // Readonly variable
4449 if ( $scope.page > value ) {
4450 $scope.selectPage(value);
4452 ngModelCtrl.$render();
4457 this.calculateTotalPages = function() {
4458 var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
4459 return Math.max(totalPages || 0, 1);
4462 this.render = function() {
4463 $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
4466 $scope.selectPage = function(page, evt) {
4468 evt.preventDefault();
4471 var clickAllowed = !$scope.ngDisabled || !evt;
4472 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
4473 if (evt && evt.target) {
4476 ngModelCtrl.$setViewValue(page);
4477 ngModelCtrl.$render();
4481 $scope.getText = function(key) {
4482 return $scope[key + 'Text'] || self.config[key + 'Text'];
4485 $scope.noPrevious = function() {
4486 return $scope.page === 1;
4489 $scope.noNext = function() {
4490 return $scope.page === $scope.totalPages;
4494 .constant('uibPaginationConfig', {
4496 boundaryLinks: false,
4497 directionLinks: true,
4499 previousText: 'Previous',
4505 .directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, paginationConfig) {
4516 require: ['uibPagination', '?ngModel'],
4517 controller: 'UibPaginationController',
4518 controllerAs: 'pagination',
4519 templateUrl: function(element, attrs) {
4520 return attrs.templateUrl || 'template/pagination/pagination.html';
4523 link: function(scope, element, attrs, ctrls) {
4524 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4527 return; // do nothing if no ng-model
4530 // Setup configuration parameters
4531 var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
4532 rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
4533 scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
4534 scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
4536 paginationCtrl.init(ngModelCtrl, paginationConfig);
4538 if (attrs.maxSize) {
4539 scope.$parent.$watch($parse(attrs.maxSize), function(value) {
4540 maxSize = parseInt(value, 10);
4541 paginationCtrl.render();
4545 // Create page object used in template
4546 function makePage(number, text, isActive) {
4554 function getPages(currentPage, totalPages) {
4557 // Default page limits
4558 var startPage = 1, endPage = totalPages;
4559 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
4561 // recompute if maxSize
4564 // Current page is displayed in the middle of the visible ones
4565 startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
4566 endPage = startPage + maxSize - 1;
4568 // Adjust if limit is exceeded
4569 if (endPage > totalPages) {
4570 endPage = totalPages;
4571 startPage = endPage - maxSize + 1;
4574 // Visible pages are paginated with maxSize
4575 startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
4577 // Adjust last page if limit is exceeded
4578 endPage = Math.min(startPage + maxSize - 1, totalPages);
4582 // Add page number links
4583 for (var number = startPage; number <= endPage; number++) {
4584 var page = makePage(number, number, number === currentPage);
4588 // Add links to move between page sets
4589 if (isMaxSized && ! rotate) {
4590 if (startPage > 1) {
4591 var previousPageSet = makePage(startPage - 1, '...', false);
4592 pages.unshift(previousPageSet);
4595 if (endPage < totalPages) {
4596 var nextPageSet = makePage(endPage + 1, '...', false);
4597 pages.push(nextPageSet);
4604 var originalRender = paginationCtrl.render;
4605 paginationCtrl.render = function() {
4607 if (scope.page > 0 && scope.page <= scope.totalPages) {
4608 scope.pages = getPages(scope.page, scope.totalPages);
4615 .constant('uibPagerConfig', {
4617 previousText: '« Previous',
4618 nextText: 'Next »',
4622 .directive('uibPager', ['uibPagerConfig', function(pagerConfig) {
4631 require: ['uibPager', '?ngModel'],
4632 controller: 'UibPaginationController',
4633 controllerAs: 'pagination',
4634 templateUrl: function(element, attrs) {
4635 return attrs.templateUrl || 'template/pagination/pager.html';
4638 link: function(scope, element, attrs, ctrls) {
4639 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4642 return; // do nothing if no ng-model
4645 scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
4646 paginationCtrl.init(ngModelCtrl, pagerConfig);
4651 /* Deprecated Pagination Below */
4653 angular.module('ui.bootstrap.pagination')
4654 .value('$paginationSuppressWarning', false)
4655 .controller('PaginationController', ['$scope', '$attrs', '$parse', '$log', '$paginationSuppressWarning', function($scope, $attrs, $parse, $log, $paginationSuppressWarning) {
4656 if (!$paginationSuppressWarning) {
4657 $log.warn('PaginationController is now deprecated. Use UibPaginationController instead.');
4661 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
4662 setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
4664 this.init = function(ngModelCtrl_, config) {
4665 ngModelCtrl = ngModelCtrl_;
4666 this.config = config;
4668 ngModelCtrl.$render = function() {
4672 if ($attrs.itemsPerPage) {
4673 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
4674 self.itemsPerPage = parseInt(value, 10);
4675 $scope.totalPages = self.calculateTotalPages();
4678 this.itemsPerPage = config.itemsPerPage;
4681 $scope.$watch('totalItems', function() {
4682 $scope.totalPages = self.calculateTotalPages();
4685 $scope.$watch('totalPages', function(value) {
4686 setNumPages($scope.$parent, value); // Readonly variable
4688 if ( $scope.page > value ) {
4689 $scope.selectPage(value);
4691 ngModelCtrl.$render();
4696 this.calculateTotalPages = function() {
4697 var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
4698 return Math.max(totalPages || 0, 1);
4701 this.render = function() {
4702 $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
4705 $scope.selectPage = function(page, evt) {
4707 evt.preventDefault();
4710 var clickAllowed = !$scope.ngDisabled || !evt;
4711 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
4712 if (evt && evt.target) {
4715 ngModelCtrl.$setViewValue(page);
4716 ngModelCtrl.$render();
4720 $scope.getText = function(key) {
4721 return $scope[key + 'Text'] || self.config[key + 'Text'];
4724 $scope.noPrevious = function() {
4725 return $scope.page === 1;
4728 $scope.noNext = function() {
4729 return $scope.page === $scope.totalPages;
4732 .directive('pagination', ['$parse', 'uibPaginationConfig', '$log', '$paginationSuppressWarning', function($parse, paginationConfig, $log, $paginationSuppressWarning) {
4743 require: ['pagination', '?ngModel'],
4744 controller: 'PaginationController',
4745 controllerAs: 'pagination',
4746 templateUrl: function(element, attrs) {
4747 return attrs.templateUrl || 'template/pagination/pagination.html';
4750 link: function(scope, element, attrs, ctrls) {
4751 if (!$paginationSuppressWarning) {
4752 $log.warn('pagination is now deprecated. Use uib-pagination instead.');
4754 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4757 return; // do nothing if no ng-model
4760 // Setup configuration parameters
4761 var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
4762 rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
4763 scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
4764 scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
4766 paginationCtrl.init(ngModelCtrl, paginationConfig);
4768 if (attrs.maxSize) {
4769 scope.$parent.$watch($parse(attrs.maxSize), function(value) {
4770 maxSize = parseInt(value, 10);
4771 paginationCtrl.render();
4775 // Create page object used in template
4776 function makePage(number, text, isActive) {
4784 function getPages(currentPage, totalPages) {
4787 // Default page limits
4788 var startPage = 1, endPage = totalPages;
4789 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
4791 // recompute if maxSize
4794 // Current page is displayed in the middle of the visible ones
4795 startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
4796 endPage = startPage + maxSize - 1;
4798 // Adjust if limit is exceeded
4799 if (endPage > totalPages) {
4800 endPage = totalPages;
4801 startPage = endPage - maxSize + 1;
4804 // Visible pages are paginated with maxSize
4805 startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
4807 // Adjust last page if limit is exceeded
4808 endPage = Math.min(startPage + maxSize - 1, totalPages);
4812 // Add page number links
4813 for (var number = startPage; number <= endPage; number++) {
4814 var page = makePage(number, number, number === currentPage);
4818 // Add links to move between page sets
4819 if (isMaxSized && ! rotate) {
4820 if (startPage > 1) {
4821 var previousPageSet = makePage(startPage - 1, '...', false);
4822 pages.unshift(previousPageSet);
4825 if (endPage < totalPages) {
4826 var nextPageSet = makePage(endPage + 1, '...', false);
4827 pages.push(nextPageSet);
4834 var originalRender = paginationCtrl.render;
4835 paginationCtrl.render = function() {
4837 if (scope.page > 0 && scope.page <= scope.totalPages) {
4838 scope.pages = getPages(scope.page, scope.totalPages);
4845 .directive('pager', ['uibPagerConfig', '$log', '$paginationSuppressWarning', function(pagerConfig, $log, $paginationSuppressWarning) {
4854 require: ['pager', '?ngModel'],
4855 controller: 'PaginationController',
4856 controllerAs: 'pagination',
4857 templateUrl: function(element, attrs) {
4858 return attrs.templateUrl || 'template/pagination/pager.html';
4861 link: function(scope, element, attrs, ctrls) {
4862 if (!$paginationSuppressWarning) {
4863 $log.warn('pager is now deprecated. Use uib-pager instead.');
4865 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4868 return; // do nothing if no ng-model
4871 scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
4872 paginationCtrl.init(ngModelCtrl, pagerConfig);
4878 * The following features are still outstanding: animation as a
4879 * function, placement as a function, inside, support for more triggers than
4880 * just mouse enter/leave, html tooltips, and selector delegation.
4882 angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
4885 * The $tooltip service creates tooltip- and popover-like directives as well as
4886 * houses global options for them.
4888 .provider('$uibTooltip', function() {
4889 // The default options tooltip and popover.
4890 var defaultOptions = {
4895 useContentExp: false
4898 // Default hide triggers for each show trigger
4900 'mouseenter': 'mouseleave',
4906 // The options specified to the provider globally.
4907 var globalOptions = {};
4910 * `options({})` allows global configuration of all tooltips in the
4913 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
4914 * // place tooltips left instead of top by default
4915 * $tooltipProvider.options( { placement: 'left' } );
4918 this.options = function(value) {
4919 angular.extend(globalOptions, value);
4923 * This allows you to extend the set of trigger mappings available. E.g.:
4925 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
4927 this.setTriggers = function setTriggers(triggers) {
4928 angular.extend(triggerMap, triggers);
4932 * This is a helper function for translating camel-case to snake-case.
4934 function snake_case(name) {
4935 var regexp = /[A-Z]/g;
4936 var separator = '-';
4937 return name.replace(regexp, function(letter, pos) {
4938 return (pos ? separator : '') + letter.toLowerCase();
4943 * Returns the actual instance of the $tooltip service.
4944 * TODO support multiple triggers
4946 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
4947 var openedTooltips = $$stackedMap.createNew();
4948 $document.on('keypress', function(e) {
4949 if (e.which === 27) {
4950 var last = openedTooltips.top();
4953 openedTooltips.removeTop();
4959 return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
4960 options = angular.extend({}, defaultOptions, globalOptions, options);
4963 * Returns an object of show and hide triggers.
4965 * If a trigger is supplied,
4966 * it is used to show the tooltip; otherwise, it will use the `trigger`
4967 * option passed to the `$tooltipProvider.options` method; else it will
4968 * default to the trigger supplied to this directive factory.
4970 * The hide trigger is based on the show trigger. If the `trigger` option
4971 * was passed to the `$tooltipProvider.options` method, it will use the
4972 * mapped trigger from `triggerMap` or the passed trigger if the map is
4973 * undefined; otherwise, it uses the `triggerMap` value of the show
4974 * trigger; else it will just use the show trigger.
4976 function getTriggers(trigger) {
4977 var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
4978 var hide = show.map(function(trigger) {
4979 return triggerMap[trigger] || trigger;
4987 var directiveName = snake_case(ttType);
4989 var startSym = $interpolate.startSymbol();
4990 var endSym = $interpolate.endSymbol();
4992 '<div '+ directiveName + '-popup '+
4993 'title="' + startSym + 'title' + endSym + '" '+
4994 (options.useContentExp ?
4995 'content-exp="contentExp()" ' :
4996 'content="' + startSym + 'content' + endSym + '" ') +
4997 'placement="' + startSym + 'placement' + endSym + '" '+
4998 'popup-class="' + startSym + 'popupClass' + endSym + '" '+
4999 'animation="animation" ' +
5000 'is-open="isOpen"' +
5001 'origin-scope="origScope" ' +
5002 'style="visibility: hidden; display: block; top: -9999px; left: -9999px;"' +
5007 compile: function(tElem, tAttrs) {
5008 var tooltipLinker = $compile(template);
5010 return function link(scope, element, attrs, tooltipCtrl) {
5012 var tooltipLinkedScope;
5013 var transitionTimeout;
5016 var positionTimeout;
5017 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
5018 var triggers = getTriggers(undefined);
5019 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
5020 var ttScope = scope.$new(true);
5021 var repositionScheduled = false;
5022 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
5023 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
5026 var positionTooltip = function() {
5027 // check if tooltip exists and is not empty
5028 if (!tooltip || !tooltip.html()) { return; }
5030 if (!positionTimeout) {
5031 positionTimeout = $timeout(function() {
5032 // Reset the positioning.
5033 tooltip.css({ top: 0, left: 0 });
5035 // Now set the calculated positioning.
5036 var ttCss = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
5039 ttCss.visibility = 'visible';
5042 positionTimeout = null;
5047 // Set up the correct scope to allow transclusion later
5048 ttScope.origScope = scope;
5050 // By default, the tooltip is not open.
5051 // TODO add ability to start tooltip opened
5052 ttScope.isOpen = false;
5053 openedTooltips.add(ttScope, {
5057 function toggleTooltipBind() {
5058 if (!ttScope.isOpen) {
5065 // Show the tooltip with delay if specified, otherwise show it immediately
5066 function showTooltipBind() {
5067 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
5074 if (ttScope.popupDelay) {
5075 // Do nothing if the tooltip was already scheduled to pop-up.
5076 // This happens if show is triggered multiple times before any hide is triggered.
5078 showTimeout = $timeout(show, ttScope.popupDelay, false);
5085 function hideTooltipBind() {
5088 if (ttScope.popupCloseDelay) {
5090 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
5097 // Show the tooltip popup element.
5102 // Don't show empty tooltips.
5103 if (!ttScope.content) {
5104 return angular.noop;
5109 // And show the tooltip.
5110 ttScope.$evalAsync(function() {
5111 ttScope.isOpen = true;
5117 function cancelShow() {
5119 $timeout.cancel(showTimeout);
5123 if (positionTimeout) {
5124 $timeout.cancel(positionTimeout);
5125 positionTimeout = null;
5129 // Hide the tooltip popup element.
5138 // First things first: we don't show it anymore.
5139 ttScope.$evalAsync(function() {
5140 ttScope.isOpen = false;
5141 assignIsOpen(false);
5142 // And now we remove it from the DOM. However, if we have animation, we
5143 // need to wait for it to expire beforehand.
5144 // FIXME: this is a placeholder for a port of the transitions library.
5145 // The fade transition in TWBS is 150ms.
5146 if (ttScope.animation) {
5147 if (!transitionTimeout) {
5148 transitionTimeout = $timeout(removeTooltip, 150, false);
5156 function cancelHide() {
5158 $timeout.cancel(hideTimeout);
5161 if (transitionTimeout) {
5162 $timeout.cancel(transitionTimeout);
5163 transitionTimeout = null;
5167 function createTooltip() {
5168 // There can only be one tooltip element per directive shown at once.
5173 tooltipLinkedScope = ttScope.$new();
5174 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
5176 $document.find('body').append(tooltip);
5178 element.after(tooltip);
5185 function removeTooltip() {
5186 unregisterObservers();
5188 transitionTimeout = null;
5193 if (tooltipLinkedScope) {
5194 tooltipLinkedScope.$destroy();
5195 tooltipLinkedScope = null;
5200 * Set the inital scope values. Once
5201 * the tooltip is created, the observers
5202 * will be added to keep things in synch.
5204 function prepareTooltip() {
5205 ttScope.title = attrs[prefix + 'Title'];
5207 ttScope.content = contentParse(scope);
5209 ttScope.content = attrs[ttType];
5212 ttScope.popupClass = attrs[prefix + 'Class'];
5213 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
5215 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
5216 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
5217 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
5218 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
5221 function assignIsOpen(isOpen) {
5222 if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
5223 isOpenParse.assign(scope, isOpen);
5227 ttScope.contentExp = function() {
5228 return ttScope.content;
5232 * Observe the relevant attributes.
5234 attrs.$observe('disabled', function(val) {
5239 if (val && ttScope.isOpen) {
5245 scope.$watch(isOpenParse, function(val) {
5247 if (ttScope && !val === ttScope.isOpen) {
5248 toggleTooltipBind();
5254 function prepObservers() {
5255 observers.length = 0;
5259 scope.$watch(contentParse, function(val) {
5260 ttScope.content = val;
5261 if (!val && ttScope.isOpen) {
5268 tooltipLinkedScope.$watch(function() {
5269 if (!repositionScheduled) {
5270 repositionScheduled = true;
5271 tooltipLinkedScope.$$postDigest(function() {
5272 repositionScheduled = false;
5273 if (ttScope && ttScope.isOpen) {
5282 attrs.$observe(ttType, function(val) {
5283 ttScope.content = val;
5284 if (!val && ttScope.isOpen) {
5294 attrs.$observe(prefix + 'Title', function(val) {
5295 ttScope.title = val;
5296 if (ttScope.isOpen) {
5303 attrs.$observe(prefix + 'Placement', function(val) {
5304 ttScope.placement = val ? val : options.placement;
5305 if (ttScope.isOpen) {
5312 function unregisterObservers() {
5313 if (observers.length) {
5314 angular.forEach(observers, function(observer) {
5317 observers.length = 0;
5321 var unregisterTriggers = function() {
5322 triggers.show.forEach(function(trigger) {
5323 element.unbind(trigger, showTooltipBind);
5325 triggers.hide.forEach(function(trigger) {
5326 trigger.split(' ').forEach(function(hideTrigger) {
5327 element[0].removeEventListener(hideTrigger, hideTooltipBind);
5332 function prepTriggers() {
5333 var val = attrs[prefix + 'Trigger'];
5334 unregisterTriggers();
5336 triggers = getTriggers(val);
5338 if (triggers.show !== 'none') {
5339 triggers.show.forEach(function(trigger, idx) {
5340 // Using raw addEventListener due to jqLite/jQuery bug - #4060
5341 if (trigger === triggers.hide[idx]) {
5342 element[0].addEventListener(trigger, toggleTooltipBind);
5343 } else if (trigger) {
5344 element[0].addEventListener(trigger, showTooltipBind);
5345 triggers.hide[idx].split(' ').forEach(function(trigger) {
5346 element[0].addEventListener(trigger, hideTooltipBind);
5350 element.on('keypress', function(e) {
5351 if (e.which === 27) {
5361 var animation = scope.$eval(attrs[prefix + 'Animation']);
5362 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
5364 var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']);
5365 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
5367 // if a tooltip is attached to <body> we need to remove it on
5368 // location change as its parent scope will probably not be destroyed
5371 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
5372 if (ttScope.isOpen) {
5378 // Make sure tooltip is destroyed and removed.
5379 scope.$on('$destroy', function onDestroyTooltip() {
5382 unregisterTriggers();
5384 openedTooltips.remove(ttScope);
5394 // This is mostly ngInclude code but with a custom scope
5395 .directive('uibTooltipTemplateTransclude', [
5396 '$animate', '$sce', '$compile', '$templateRequest',
5397 function ($animate , $sce , $compile , $templateRequest) {
5399 link: function(scope, elem, attrs) {
5400 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
5402 var changeCounter = 0,
5407 var cleanupLastIncludeContent = function() {
5408 if (previousElement) {
5409 previousElement.remove();
5410 previousElement = null;
5414 currentScope.$destroy();
5415 currentScope = null;
5418 if (currentElement) {
5419 $animate.leave(currentElement).then(function() {
5420 previousElement = null;
5422 previousElement = currentElement;
5423 currentElement = null;
5427 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
5428 var thisChangeId = ++changeCounter;
5431 //set the 2nd param to true to ignore the template request error so that the inner
5432 //contents and scope can be cleaned up.
5433 $templateRequest(src, true).then(function(response) {
5434 if (thisChangeId !== changeCounter) { return; }
5435 var newScope = origScope.$new();
5436 var template = response;
5438 var clone = $compile(template)(newScope, function(clone) {
5439 cleanupLastIncludeContent();
5440 $animate.enter(clone, elem);
5443 currentScope = newScope;
5444 currentElement = clone;
5446 currentScope.$emit('$includeContentLoaded', src);
5448 if (thisChangeId === changeCounter) {
5449 cleanupLastIncludeContent();
5450 scope.$emit('$includeContentError', src);
5453 scope.$emit('$includeContentRequested', src);
5455 cleanupLastIncludeContent();
5459 scope.$on('$destroy', cleanupLastIncludeContent);
5465 * Note that it's intentional that these classes are *not* applied through $animate.
5466 * They must not be animated as they're expected to be present on the tooltip on
5469 .directive('uibTooltipClasses', function() {
5472 link: function(scope, element, attrs) {
5473 if (scope.placement) {
5474 element.addClass(scope.placement);
5477 if (scope.popupClass) {
5478 element.addClass(scope.popupClass);
5481 if (scope.animation()) {
5482 element.addClass(attrs.tooltipAnimationClass);
5488 .directive('uibTooltipPopup', function() {
5491 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5492 templateUrl: 'template/tooltip/tooltip-popup.html',
5493 link: function(scope, element) {
5494 element.addClass('tooltip');
5499 .directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
5500 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
5503 .directive('uibTooltipTemplatePopup', function() {
5506 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5508 templateUrl: 'template/tooltip/tooltip-template-popup.html',
5509 link: function(scope, element) {
5510 element.addClass('tooltip');
5515 .directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
5516 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
5521 .directive('uibTooltipHtmlPopup', function() {
5524 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5525 templateUrl: 'template/tooltip/tooltip-html-popup.html',
5526 link: function(scope, element) {
5527 element.addClass('tooltip');
5532 .directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
5533 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
5538 /* Deprecated tooltip below */
5540 angular.module('ui.bootstrap.tooltip')
5542 .value('$tooltipSuppressWarning', false)
5544 .provider('$tooltip', ['$uibTooltipProvider', function($uibTooltipProvider) {
5545 angular.extend(this, $uibTooltipProvider);
5547 this.$get = ['$log', '$tooltipSuppressWarning', '$injector', function($log, $tooltipSuppressWarning, $injector) {
5548 if (!$tooltipSuppressWarning) {
5549 $log.warn('$tooltip is now deprecated. Use $uibTooltip instead.');
5552 return $injector.invoke($uibTooltipProvider.$get);
5556 // This is mostly ngInclude code but with a custom scope
5557 .directive('tooltipTemplateTransclude', [
5558 '$animate', '$sce', '$compile', '$templateRequest', '$log', '$tooltipSuppressWarning',
5559 function ($animate , $sce , $compile , $templateRequest, $log, $tooltipSuppressWarning) {
5561 link: function(scope, elem, attrs) {
5562 if (!$tooltipSuppressWarning) {
5563 $log.warn('tooltip-template-transclude is now deprecated. Use uib-tooltip-template-transclude instead.');
5566 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
5568 var changeCounter = 0,
5573 var cleanupLastIncludeContent = function() {
5574 if (previousElement) {
5575 previousElement.remove();
5576 previousElement = null;
5579 currentScope.$destroy();
5580 currentScope = null;
5582 if (currentElement) {
5583 $animate.leave(currentElement).then(function() {
5584 previousElement = null;
5586 previousElement = currentElement;
5587 currentElement = null;
5591 scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function(src) {
5592 var thisChangeId = ++changeCounter;
5595 //set the 2nd param to true to ignore the template request error so that the inner
5596 //contents and scope can be cleaned up.
5597 $templateRequest(src, true).then(function(response) {
5598 if (thisChangeId !== changeCounter) { return; }
5599 var newScope = origScope.$new();
5600 var template = response;
5602 var clone = $compile(template)(newScope, function(clone) {
5603 cleanupLastIncludeContent();
5604 $animate.enter(clone, elem);
5607 currentScope = newScope;
5608 currentElement = clone;
5610 currentScope.$emit('$includeContentLoaded', src);
5612 if (thisChangeId === changeCounter) {
5613 cleanupLastIncludeContent();
5614 scope.$emit('$includeContentError', src);
5617 scope.$emit('$includeContentRequested', src);
5619 cleanupLastIncludeContent();
5623 scope.$on('$destroy', cleanupLastIncludeContent);
5628 .directive('tooltipClasses', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
5631 link: function(scope, element, attrs) {
5632 if (!$tooltipSuppressWarning) {
5633 $log.warn('tooltip-classes is now deprecated. Use uib-tooltip-classes instead.');
5636 if (scope.placement) {
5637 element.addClass(scope.placement);
5639 if (scope.popupClass) {
5640 element.addClass(scope.popupClass);
5642 if (scope.animation()) {
5643 element.addClass(attrs.tooltipAnimationClass);
5649 .directive('tooltipPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
5652 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5653 templateUrl: 'template/tooltip/tooltip-popup.html',
5654 link: function(scope, element) {
5655 if (!$tooltipSuppressWarning) {
5656 $log.warn('tooltip-popup is now deprecated. Use uib-tooltip-popup instead.');
5659 element.addClass('tooltip');
5664 .directive('tooltip', ['$tooltip', function($tooltip) {
5665 return $tooltip('tooltip', 'tooltip', 'mouseenter');
5668 .directive('tooltipTemplatePopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
5671 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5673 templateUrl: 'template/tooltip/tooltip-template-popup.html',
5674 link: function(scope, element) {
5675 if (!$tooltipSuppressWarning) {
5676 $log.warn('tooltip-template-popup is now deprecated. Use uib-tooltip-template-popup instead.');
5679 element.addClass('tooltip');
5684 .directive('tooltipTemplate', ['$tooltip', function($tooltip) {
5685 return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
5690 .directive('tooltipHtmlPopup', ['$log', '$tooltipSuppressWarning', function($log, $tooltipSuppressWarning) {
5693 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5694 templateUrl: 'template/tooltip/tooltip-html-popup.html',
5695 link: function(scope, element) {
5696 if (!$tooltipSuppressWarning) {
5697 $log.warn('tooltip-html-popup is now deprecated. Use uib-tooltip-html-popup instead.');
5700 element.addClass('tooltip');
5705 .directive('tooltipHtml', ['$tooltip', function($tooltip) {
5706 return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', {
5712 * The following features are still outstanding: popup delay, animation as a
5713 * function, placement as a function, inside, support for more triggers than
5714 * just mouse enter/leave, and selector delegatation.
5716 angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
5718 .directive('uibPopoverTemplatePopup', function() {
5721 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5723 templateUrl: 'template/popover/popover-template.html',
5724 link: function(scope, element) {
5725 element.addClass('popover');
5730 .directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
5731 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
5736 .directive('uibPopoverHtmlPopup', function() {
5739 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5740 templateUrl: 'template/popover/popover-html.html',
5741 link: function(scope, element) {
5742 element.addClass('popover');
5747 .directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
5748 return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
5753 .directive('uibPopoverPopup', function() {
5756 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5757 templateUrl: 'template/popover/popover.html',
5758 link: function(scope, element) {
5759 element.addClass('popover');
5764 .directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
5765 return $uibTooltip('uibPopover', 'popover', 'click');
5768 /* Deprecated popover below */
5770 angular.module('ui.bootstrap.popover')
5772 .value('$popoverSuppressWarning', false)
5774 .directive('popoverTemplatePopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
5777 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5779 templateUrl: 'template/popover/popover-template.html',
5780 link: function(scope, element) {
5781 if (!$popoverSuppressWarning) {
5782 $log.warn('popover-template-popup is now deprecated. Use uib-popover-template-popup instead.');
5785 element.addClass('popover');
5790 .directive('popoverTemplate', ['$tooltip', function($tooltip) {
5791 return $tooltip('popoverTemplate', 'popover', 'click', {
5796 .directive('popoverHtmlPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
5799 scope: { contentExp: '&', title: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5800 templateUrl: 'template/popover/popover-html.html',
5801 link: function(scope, element) {
5802 if (!$popoverSuppressWarning) {
5803 $log.warn('popover-html-popup is now deprecated. Use uib-popover-html-popup instead.');
5806 element.addClass('popover');
5811 .directive('popoverHtml', ['$tooltip', function($tooltip) {
5812 return $tooltip('popoverHtml', 'popover', 'click', {
5817 .directive('popoverPopup', ['$log', '$popoverSuppressWarning', function($log, $popoverSuppressWarning) {
5820 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5821 templateUrl: 'template/popover/popover.html',
5822 link: function(scope, element) {
5823 if (!$popoverSuppressWarning) {
5824 $log.warn('popover-popup is now deprecated. Use uib-popover-popup instead.');
5827 element.addClass('popover');
5832 .directive('popover', ['$tooltip', function($tooltip) {
5834 return $tooltip('popover', 'popover', 'click');
5837 angular.module('ui.bootstrap.progressbar', [])
5839 .constant('uibProgressConfig', {
5844 .controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
5846 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
5849 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
5851 this.addBar = function(bar, element, attrs) {
5853 element.css({'transition': 'none'});
5856 this.bars.push(bar);
5858 bar.max = $scope.max;
5859 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
5861 bar.$watch('value', function(value) {
5862 bar.recalculatePercentage();
5865 bar.recalculatePercentage = function() {
5866 var totalPercentage = self.bars.reduce(function(total, bar) {
5867 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
5868 return total + bar.percent;
5871 if (totalPercentage > 100) {
5872 bar.percent -= totalPercentage - 100;
5876 bar.$on('$destroy', function() {
5878 self.removeBar(bar);
5882 this.removeBar = function(bar) {
5883 this.bars.splice(this.bars.indexOf(bar), 1);
5884 this.bars.forEach(function (bar) {
5885 bar.recalculatePercentage();
5889 $scope.$watch('max', function(max) {
5890 self.bars.forEach(function(bar) {
5891 bar.max = $scope.max;
5892 bar.recalculatePercentage();
5897 .directive('uibProgress', function() {
5901 controller: 'UibProgressController',
5902 require: 'uibProgress',
5906 templateUrl: 'template/progressbar/progress.html'
5910 .directive('uibBar', function() {
5914 require: '^uibProgress',
5919 templateUrl: 'template/progressbar/bar.html',
5920 link: function(scope, element, attrs, progressCtrl) {
5921 progressCtrl.addBar(scope, element, attrs);
5926 .directive('uibProgressbar', function() {
5930 controller: 'UibProgressController',
5936 templateUrl: 'template/progressbar/progressbar.html',
5937 link: function(scope, element, attrs, progressCtrl) {
5938 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
5943 /* Deprecated progressbar below */
5945 angular.module('ui.bootstrap.progressbar')
5947 .value('$progressSuppressWarning', false)
5949 .controller('ProgressController', ['$scope', '$attrs', 'uibProgressConfig', '$log', '$progressSuppressWarning', function($scope, $attrs, progressConfig, $log, $progressSuppressWarning) {
5950 if (!$progressSuppressWarning) {
5951 $log.warn('ProgressController is now deprecated. Use UibProgressController instead.');
5955 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
5958 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
5960 this.addBar = function(bar, element, attrs) {
5962 element.css({'transition': 'none'});
5965 this.bars.push(bar);
5967 bar.max = $scope.max;
5968 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
5970 bar.$watch('value', function(value) {
5971 bar.recalculatePercentage();
5974 bar.recalculatePercentage = function() {
5975 bar.percent = +(100 * bar.value / bar.max).toFixed(2);
5977 var totalPercentage = self.bars.reduce(function(total, bar) {
5978 return total + bar.percent;
5981 if (totalPercentage > 100) {
5982 bar.percent -= totalPercentage - 100;
5986 bar.$on('$destroy', function() {
5988 self.removeBar(bar);
5992 this.removeBar = function(bar) {
5993 this.bars.splice(this.bars.indexOf(bar), 1);
5996 $scope.$watch('max', function(max) {
5997 self.bars.forEach(function(bar) {
5998 bar.max = $scope.max;
5999 bar.recalculatePercentage();
6004 .directive('progress', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
6008 controller: 'ProgressController',
6009 require: 'progress',
6014 templateUrl: 'template/progressbar/progress.html',
6016 if (!$progressSuppressWarning) {
6017 $log.warn('progress is now deprecated. Use uib-progress instead.');
6023 .directive('bar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
6027 require: '^progress',
6032 templateUrl: 'template/progressbar/bar.html',
6033 link: function(scope, element, attrs, progressCtrl) {
6034 if (!$progressSuppressWarning) {
6035 $log.warn('bar is now deprecated. Use uib-bar instead.');
6037 progressCtrl.addBar(scope, element);
6042 .directive('progressbar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
6046 controller: 'ProgressController',
6052 templateUrl: 'template/progressbar/progressbar.html',
6053 link: function(scope, element, attrs, progressCtrl) {
6054 if (!$progressSuppressWarning) {
6055 $log.warn('progressbar is now deprecated. Use uib-progressbar instead.');
6057 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
6062 angular.module('ui.bootstrap.rating', [])
6064 .constant('uibRatingConfig', {
6068 titles : ['one', 'two', 'three', 'four', 'five']
6071 .controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
6072 var ngModelCtrl = { $setViewValue: angular.noop };
6074 this.init = function(ngModelCtrl_) {
6075 ngModelCtrl = ngModelCtrl_;
6076 ngModelCtrl.$render = this.render;
6078 ngModelCtrl.$formatters.push(function(value) {
6079 if (angular.isNumber(value) && value << 0 !== value) {
6080 value = Math.round(value);
6085 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
6086 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
6087 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
6088 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
6089 tmpTitles : ratingConfig.titles;
6091 var ratingStates = angular.isDefined($attrs.ratingStates) ?
6092 $scope.$parent.$eval($attrs.ratingStates) :
6093 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
6094 $scope.range = this.buildTemplateObjects(ratingStates);
6097 this.buildTemplateObjects = function(states) {
6098 for (var i = 0, n = states.length; i < n; i++) {
6099 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
6104 this.getTitle = function(index) {
6105 if (index >= this.titles.length) {
6108 return this.titles[index];
6112 $scope.rate = function(value) {
6113 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
6114 ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
6115 ngModelCtrl.$render();
6119 $scope.enter = function(value) {
6120 if (!$scope.readonly) {
6121 $scope.value = value;
6123 $scope.onHover({value: value});
6126 $scope.reset = function() {
6127 $scope.value = ngModelCtrl.$viewValue;
6131 $scope.onKeydown = function(evt) {
6132 if (/(37|38|39|40)/.test(evt.which)) {
6133 evt.preventDefault();
6134 evt.stopPropagation();
6135 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
6139 this.render = function() {
6140 $scope.value = ngModelCtrl.$viewValue;
6144 .directive('uibRating', function() {
6146 require: ['uibRating', 'ngModel'],
6152 controller: 'UibRatingController',
6153 templateUrl: 'template/rating/rating.html',
6155 link: function(scope, element, attrs, ctrls) {
6156 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
6157 ratingCtrl.init(ngModelCtrl);
6162 /* Deprecated rating below */
6164 angular.module('ui.bootstrap.rating')
6166 .value('$ratingSuppressWarning', false)
6168 .controller('RatingController', ['$scope', '$attrs', '$controller', '$log', '$ratingSuppressWarning', function($scope, $attrs, $controller, $log, $ratingSuppressWarning) {
6169 if (!$ratingSuppressWarning) {
6170 $log.warn('RatingController is now deprecated. Use UibRatingController instead.');
6173 angular.extend(this, $controller('UibRatingController', {
6179 .directive('rating', ['$log', '$ratingSuppressWarning', function($log, $ratingSuppressWarning) {
6181 require: ['rating', 'ngModel'],
6187 controller: 'RatingController',
6188 templateUrl: 'template/rating/rating.html',
6190 link: function(scope, element, attrs, ctrls) {
6191 if (!$ratingSuppressWarning) {
6192 $log.warn('rating is now deprecated. Use uib-rating instead.');
6194 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
6195 ratingCtrl.init(ngModelCtrl);
6203 * @name ui.bootstrap.tabs
6206 * AngularJS version of the tabs directive.
6209 angular.module('ui.bootstrap.tabs', [])
6211 .controller('UibTabsetController', ['$scope', function ($scope) {
6213 tabs = ctrl.tabs = $scope.tabs = [];
6215 ctrl.select = function(selectedTab) {
6216 angular.forEach(tabs, function(tab) {
6217 if (tab.active && tab !== selectedTab) {
6220 selectedTab.selectCalled = false;
6223 selectedTab.active = true;
6224 // only call select if it has not already been called
6225 if (!selectedTab.selectCalled) {
6226 selectedTab.onSelect();
6227 selectedTab.selectCalled = true;
6231 ctrl.addTab = function addTab(tab) {
6233 // we can't run the select function on the first tab
6234 // since that would select it twice
6235 if (tabs.length === 1 && tab.active !== false) {
6237 } else if (tab.active) {
6244 ctrl.removeTab = function removeTab(tab) {
6245 var index = tabs.indexOf(tab);
6246 //Select a new tab if the tab to be removed is selected and not destroyed
6247 if (tab.active && tabs.length > 1 && !destroyed) {
6248 //If this is the last tab, select the previous tab. else, the next tab.
6249 var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
6250 ctrl.select(tabs[newActiveIndex]);
6252 tabs.splice(index, 1);
6256 $scope.$on('$destroy', function() {
6263 * @name ui.bootstrap.tabs.directive:tabset
6267 * Tabset is the outer container for the tabs directive
6269 * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
6270 * @param {boolean=} justified Whether or not to use justified styling for the tabs.
6273 <example module="ui.bootstrap">
6274 <file name="index.html">
6276 <uib-tab heading="Tab 1"><b>First</b> Content!</uib-tab>
6277 <uib-tab heading="Tab 2"><i>Second</i> Content!</uib-tab>
6280 <uib-tabset vertical="true">
6281 <uib-tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</uib-tab>
6282 <uib-tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</uib-tab>
6284 <uib-tabset justified="true">
6285 <uib-tab heading="Justified Tab 1"><b>First</b> Justified Content!</uib-tab>
6286 <uib-tab heading="Justified Tab 2"><i>Second</i> Justified Content!</uib-tab>
6291 .directive('uibTabset', function() {
6299 controller: 'UibTabsetController',
6300 templateUrl: 'template/tabs/tabset.html',
6301 link: function(scope, element, attrs) {
6302 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
6303 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
6310 * @name ui.bootstrap.tabs.directive:tab
6313 * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
6314 * @param {string=} select An expression to evaluate when the tab is selected.
6315 * @param {boolean=} active A binding, telling whether or not this tab is selected.
6316 * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
6319 * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
6322 <example module="ui.bootstrap">
6323 <file name="index.html">
6324 <div ng-controller="TabsDemoCtrl">
6325 <button class="btn btn-small" ng-click="items[0].active = true">
6326 Select item 1, using active binding
6328 <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
6329 Enable/disable item 2, using disabled binding
6333 <uib-tab heading="Tab 1">First Tab</uib-tab>
6334 <uib-tab select="alertMe()">
6335 <uib-tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
6336 Second Tab, with alert callback and html heading!
6338 <uib-tab ng-repeat="item in items"
6339 heading="{{item.title}}"
6340 disabled="item.disabled"
6341 active="item.active">
6347 <file name="script.js">
6348 function TabsDemoCtrl($scope) {
6350 { title:"Dynamic Title 1", content:"Dynamic Item 0" },
6351 { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
6354 $scope.alertMe = function() {
6355 setTimeout(function() {
6356 alert("You've selected the alert tab!");
6366 * @name ui.bootstrap.tabs.directive:tabHeading
6370 * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
6373 <example module="ui.bootstrap">
6374 <file name="index.html">
6377 <uib-tab-heading><b>HTML</b> in my titles?!</tab-heading>
6378 And some content, too!
6381 <uib-tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
6388 .directive('uibTab', ['$parse', function($parse) {
6390 require: '^uibTabset',
6393 templateUrl: 'template/tabs/tab.html',
6398 onSelect: '&select', //This callback is called in contentHeadingTransclude
6399 //once it inserts the tab's content into the dom
6400 onDeselect: '&deselect'
6402 controller: function() {
6403 //Empty controller so other directives can require being 'under' a tab
6405 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
6406 scope.$watch('active', function(active) {
6408 tabsetCtrl.select(scope);
6412 scope.disabled = false;
6413 if (attrs.disable) {
6414 scope.$parent.$watch($parse(attrs.disable), function(value) {
6415 scope.disabled = !! value;
6419 scope.select = function() {
6420 if (!scope.disabled) {
6421 scope.active = true;
6425 tabsetCtrl.addTab(scope);
6426 scope.$on('$destroy', function() {
6427 tabsetCtrl.removeTab(scope);
6430 //We need to transclude later, once the content container is ready.
6431 //when this link happens, we're inside a tab heading.
6432 scope.$transcludeFn = transclude;
6437 .directive('uibTabHeadingTransclude', function() {
6440 require: ['?^uibTab', '?^tab'], // TODO: change to '^uibTab' after deprecation removal
6441 link: function(scope, elm) {
6442 scope.$watch('headingElement', function updateHeadingElement(heading) {
6445 elm.append(heading);
6452 .directive('uibTabContentTransclude', function() {
6455 require: ['?^uibTabset', '?^tabset'], // TODO: change to '^uibTabset' after deprecation removal
6456 link: function(scope, elm, attrs) {
6457 var tab = scope.$eval(attrs.uibTabContentTransclude);
6459 //Now our tab is ready to be transcluded: both the tab heading area
6460 //and the tab content area are loaded. Transclude 'em both.
6461 tab.$transcludeFn(tab.$parent, function(contents) {
6462 angular.forEach(contents, function(node) {
6463 if (isTabHeading(node)) {
6464 //Let tabHeadingTransclude know.
6465 tab.headingElement = node;
6474 function isTabHeading(node) {
6475 return node.tagName && (
6476 node.hasAttribute('tab-heading') || // TODO: remove after deprecation removal
6477 node.hasAttribute('data-tab-heading') || // TODO: remove after deprecation removal
6478 node.hasAttribute('x-tab-heading') || // TODO: remove after deprecation removal
6479 node.hasAttribute('uib-tab-heading') ||
6480 node.hasAttribute('data-uib-tab-heading') ||
6481 node.hasAttribute('x-uib-tab-heading') ||
6482 node.tagName.toLowerCase() === 'tab-heading' || // TODO: remove after deprecation removal
6483 node.tagName.toLowerCase() === 'data-tab-heading' || // TODO: remove after deprecation removal
6484 node.tagName.toLowerCase() === 'x-tab-heading' || // TODO: remove after deprecation removal
6485 node.tagName.toLowerCase() === 'uib-tab-heading' ||
6486 node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
6487 node.tagName.toLowerCase() === 'x-uib-tab-heading'
6492 /* deprecated tabs below */
6494 angular.module('ui.bootstrap.tabs')
6496 .value('$tabsSuppressWarning', false)
6498 .controller('TabsetController', ['$scope', '$controller', '$log', '$tabsSuppressWarning', function($scope, $controller, $log, $tabsSuppressWarning) {
6499 if (!$tabsSuppressWarning) {
6500 $log.warn('TabsetController is now deprecated. Use UibTabsetController instead.');
6503 angular.extend(this, $controller('UibTabsetController', {
6508 .directive('tabset', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
6516 controller: 'TabsetController',
6517 templateUrl: 'template/tabs/tabset.html',
6518 link: function(scope, element, attrs) {
6520 if (!$tabsSuppressWarning) {
6521 $log.warn('tabset is now deprecated. Use uib-tabset instead.');
6523 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
6524 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
6529 .directive('tab', ['$parse', '$log', '$tabsSuppressWarning', function($parse, $log, $tabsSuppressWarning) {
6534 templateUrl: 'template/tabs/tab.html',
6539 onSelect: '&select', //This callback is called in contentHeadingTransclude
6540 //once it inserts the tab's content into the dom
6541 onDeselect: '&deselect'
6543 controller: function() {
6544 //Empty controller so other directives can require being 'under' a tab
6546 link: function(scope, elm, attrs, tabsetCtrl, transclude) {
6547 if (!$tabsSuppressWarning) {
6548 $log.warn('tab is now deprecated. Use uib-tab instead.');
6551 scope.$watch('active', function(active) {
6553 tabsetCtrl.select(scope);
6557 scope.disabled = false;
6558 if (attrs.disable) {
6559 scope.$parent.$watch($parse(attrs.disable), function(value) {
6560 scope.disabled = !!value;
6564 scope.select = function() {
6565 if (!scope.disabled) {
6566 scope.active = true;
6570 tabsetCtrl.addTab(scope);
6571 scope.$on('$destroy', function() {
6572 tabsetCtrl.removeTab(scope);
6575 //We need to transclude later, once the content container is ready.
6576 //when this link happens, we're inside a tab heading.
6577 scope.$transcludeFn = transclude;
6582 .directive('tabHeadingTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
6586 link: function(scope, elm) {
6587 if (!$tabsSuppressWarning) {
6588 $log.warn('tab-heading-transclude is now deprecated. Use uib-tab-heading-transclude instead.');
6591 scope.$watch('headingElement', function updateHeadingElement(heading) {
6594 elm.append(heading);
6601 .directive('tabContentTransclude', ['$log', '$tabsSuppressWarning', function($log, $tabsSuppressWarning) {
6605 link: function(scope, elm, attrs) {
6606 if (!$tabsSuppressWarning) {
6607 $log.warn('tab-content-transclude is now deprecated. Use uib-tab-content-transclude instead.');
6610 var tab = scope.$eval(attrs.tabContentTransclude);
6612 //Now our tab is ready to be transcluded: both the tab heading area
6613 //and the tab content area are loaded. Transclude 'em both.
6614 tab.$transcludeFn(tab.$parent, function(contents) {
6615 angular.forEach(contents, function(node) {
6616 if (isTabHeading(node)) {
6617 //Let tabHeadingTransclude know.
6618 tab.headingElement = node;
6628 function isTabHeading(node) {
6629 return node.tagName && (
6630 node.hasAttribute('tab-heading') ||
6631 node.hasAttribute('data-tab-heading') ||
6632 node.hasAttribute('x-tab-heading') ||
6633 node.tagName.toLowerCase() === 'tab-heading' ||
6634 node.tagName.toLowerCase() === 'data-tab-heading' ||
6635 node.tagName.toLowerCase() === 'x-tab-heading'
6640 angular.module('ui.bootstrap.timepicker', [])
6642 .constant('uibTimepickerConfig', {
6647 readonlyInput: false,
6653 .controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
6654 var selected = new Date(),
6655 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
6656 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
6658 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
6659 $element.removeAttr('tabindex');
6661 this.init = function(ngModelCtrl_, inputs) {
6662 ngModelCtrl = ngModelCtrl_;
6663 ngModelCtrl.$render = this.render;
6665 ngModelCtrl.$formatters.unshift(function(modelValue) {
6666 return modelValue ? new Date(modelValue) : null;
6669 var hoursInputEl = inputs.eq(0),
6670 minutesInputEl = inputs.eq(1);
6672 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
6674 this.setupMousewheelEvents(hoursInputEl, minutesInputEl);
6677 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
6679 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl);
6682 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
6683 this.setupInputEvents(hoursInputEl, minutesInputEl);
6686 var hourStep = timepickerConfig.hourStep;
6687 if ($attrs.hourStep) {
6688 $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
6689 hourStep = parseInt(value, 10);
6693 var minuteStep = timepickerConfig.minuteStep;
6694 if ($attrs.minuteStep) {
6695 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
6696 minuteStep = parseInt(value, 10);
6701 $scope.$parent.$watch($parse($attrs.min), function(value) {
6702 var dt = new Date(value);
6703 min = isNaN(dt) ? undefined : dt;
6707 $scope.$parent.$watch($parse($attrs.max), function(value) {
6708 var dt = new Date(value);
6709 max = isNaN(dt) ? undefined : dt;
6712 $scope.noIncrementHours = function() {
6713 var incrementedSelected = addMinutes(selected, hourStep * 60);
6714 return incrementedSelected > max ||
6715 (incrementedSelected < selected && incrementedSelected < min);
6718 $scope.noDecrementHours = function() {
6719 var decrementedSelected = addMinutes(selected, -hourStep * 60);
6720 return decrementedSelected < min ||
6721 (decrementedSelected > selected && decrementedSelected > max);
6724 $scope.noIncrementMinutes = function() {
6725 var incrementedSelected = addMinutes(selected, minuteStep);
6726 return incrementedSelected > max ||
6727 (incrementedSelected < selected && incrementedSelected < min);
6730 $scope.noDecrementMinutes = function() {
6731 var decrementedSelected = addMinutes(selected, -minuteStep);
6732 return decrementedSelected < min ||
6733 (decrementedSelected > selected && decrementedSelected > max);
6736 $scope.noToggleMeridian = function() {
6737 if (selected.getHours() < 13) {
6738 return addMinutes(selected, 12 * 60) > max;
6740 return addMinutes(selected, -12 * 60) < min;
6745 $scope.showMeridian = timepickerConfig.showMeridian;
6746 if ($attrs.showMeridian) {
6747 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
6748 $scope.showMeridian = !!value;
6750 if (ngModelCtrl.$error.time) {
6751 // Evaluate from template
6752 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
6753 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6754 selected.setHours(hours);
6763 // Get $scope.hours in 24H mode if valid
6764 function getHoursFromTemplate() {
6765 var hours = parseInt($scope.hours, 10);
6766 var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
6771 if ($scope.showMeridian) {
6775 if ($scope.meridian === meridians[1]) {
6782 function getMinutesFromTemplate() {
6783 var minutes = parseInt($scope.minutes, 10);
6784 return (minutes >= 0 && minutes < 60) ? minutes : undefined;
6787 function pad(value) {
6788 return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString();
6791 // Respond on mousewheel spin
6792 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) {
6793 var isScrollingUp = function(e) {
6794 if (e.originalEvent) {
6795 e = e.originalEvent;
6797 //pick correct delta variable depending on event
6798 var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
6799 return (e.detail || delta > 0);
6802 hoursInputEl.bind('mousewheel wheel', function(e) {
6803 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
6807 minutesInputEl.bind('mousewheel wheel', function(e) {
6808 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
6814 // Respond on up/down arrowkeys
6815 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) {
6816 hoursInputEl.bind('keydown', function(e) {
6817 if (e.which === 38) { // up
6819 $scope.incrementHours();
6821 } else if (e.which === 40) { // down
6823 $scope.decrementHours();
6828 minutesInputEl.bind('keydown', function(e) {
6829 if (e.which === 38) { // up
6831 $scope.incrementMinutes();
6833 } else if (e.which === 40) { // down
6835 $scope.decrementMinutes();
6841 this.setupInputEvents = function(hoursInputEl, minutesInputEl) {
6842 if ($scope.readonlyInput) {
6843 $scope.updateHours = angular.noop;
6844 $scope.updateMinutes = angular.noop;
6848 var invalidate = function(invalidHours, invalidMinutes) {
6849 ngModelCtrl.$setViewValue(null);
6850 ngModelCtrl.$setValidity('time', false);
6851 if (angular.isDefined(invalidHours)) {
6852 $scope.invalidHours = invalidHours;
6854 if (angular.isDefined(invalidMinutes)) {
6855 $scope.invalidMinutes = invalidMinutes;
6859 $scope.updateHours = function() {
6860 var hours = getHoursFromTemplate(),
6861 minutes = getMinutesFromTemplate();
6863 if (angular.isDefined(hours) && angular.isDefined(minutes)) {
6864 selected.setHours(hours);
6865 if (selected < min || selected > max) {
6875 hoursInputEl.bind('blur', function(e) {
6876 if (!$scope.invalidHours && $scope.hours < 10) {
6877 $scope.$apply(function() {
6878 $scope.hours = pad($scope.hours);
6883 $scope.updateMinutes = function() {
6884 var minutes = getMinutesFromTemplate(),
6885 hours = getHoursFromTemplate();
6887 if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6888 selected.setMinutes(minutes);
6889 if (selected < min || selected > max) {
6890 invalidate(undefined, true);
6895 invalidate(undefined, true);
6899 minutesInputEl.bind('blur', function(e) {
6900 if (!$scope.invalidMinutes && $scope.minutes < 10) {
6901 $scope.$apply(function() {
6902 $scope.minutes = pad($scope.minutes);
6909 this.render = function() {
6910 var date = ngModelCtrl.$viewValue;
6913 ngModelCtrl.$setValidity('time', false);
6914 $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.');
6920 if (selected < min || selected > max) {
6921 ngModelCtrl.$setValidity('time', false);
6922 $scope.invalidHours = true;
6923 $scope.invalidMinutes = true;
6931 // Call internally when we know that model is valid.
6932 function refresh(keyboardChange) {
6934 ngModelCtrl.$setViewValue(new Date(selected));
6935 updateTemplate(keyboardChange);
6938 function makeValid() {
6939 ngModelCtrl.$setValidity('time', true);
6940 $scope.invalidHours = false;
6941 $scope.invalidMinutes = false;
6944 function updateTemplate(keyboardChange) {
6945 var hours = selected.getHours(), minutes = selected.getMinutes();
6947 if ($scope.showMeridian) {
6948 hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system
6951 $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
6952 if (keyboardChange !== 'm') {
6953 $scope.minutes = pad(minutes);
6955 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6958 function addMinutes(date, minutes) {
6959 var dt = new Date(date.getTime() + minutes * 60000);
6960 var newDate = new Date(date);
6961 newDate.setHours(dt.getHours(), dt.getMinutes());
6965 function addMinutesToSelected(minutes) {
6966 selected = addMinutes(selected, minutes);
6970 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
6971 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
6973 $scope.incrementHours = function() {
6974 if (!$scope.noIncrementHours()) {
6975 addMinutesToSelected(hourStep * 60);
6979 $scope.decrementHours = function() {
6980 if (!$scope.noDecrementHours()) {
6981 addMinutesToSelected(-hourStep * 60);
6985 $scope.incrementMinutes = function() {
6986 if (!$scope.noIncrementMinutes()) {
6987 addMinutesToSelected(minuteStep);
6991 $scope.decrementMinutes = function() {
6992 if (!$scope.noDecrementMinutes()) {
6993 addMinutesToSelected(-minuteStep);
6997 $scope.toggleMeridian = function() {
6998 if (!$scope.noToggleMeridian()) {
6999 addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1));
7004 .directive('uibTimepicker', function() {
7007 require: ['uibTimepicker', '?^ngModel'],
7008 controller: 'UibTimepickerController',
7009 controllerAs: 'timepicker',
7012 templateUrl: function(element, attrs) {
7013 return attrs.templateUrl || 'template/timepicker/timepicker.html';
7015 link: function(scope, element, attrs, ctrls) {
7016 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
7019 timepickerCtrl.init(ngModelCtrl, element.find('input'));
7025 /* Deprecated timepicker below */
7027 angular.module('ui.bootstrap.timepicker')
7029 .value('$timepickerSuppressWarning', false)
7031 .controller('TimepickerController', ['$scope', '$element', '$attrs', '$controller', '$log', '$timepickerSuppressWarning', function($scope, $element, $attrs, $controller, $log, $timepickerSuppressWarning) {
7032 if (!$timepickerSuppressWarning) {
7033 $log.warn('TimepickerController is now deprecated. Use UibTimepickerController instead.');
7036 angular.extend(this, $controller('UibTimepickerController', {
7043 .directive('timepicker', ['$log', '$timepickerSuppressWarning', function($log, $timepickerSuppressWarning) {
7046 require: ['timepicker', '?^ngModel'],
7047 controller: 'TimepickerController',
7048 controllerAs: 'timepicker',
7051 templateUrl: function(element, attrs) {
7052 return attrs.templateUrl || 'template/timepicker/timepicker.html';
7054 link: function(scope, element, attrs, ctrls) {
7055 if (!$timepickerSuppressWarning) {
7056 $log.warn('timepicker is now deprecated. Use uib-timepicker instead.');
7058 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
7061 timepickerCtrl.init(ngModelCtrl, element.find('input'));
7067 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
7070 * A helper service that can parse typeahead's syntax (string provided by users)
7071 * Extracted to a separate service for ease of unit testing
7073 .factory('uibTypeaheadParser', ['$parse', function($parse) {
7074 // 00000111000000000000022200000000000000003333333333333330000000000044000
7075 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
7077 parse: function(input) {
7078 var match = input.match(TYPEAHEAD_REGEXP);
7081 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
7082 ' but got "' + input + '".');
7087 source: $parse(match[4]),
7088 viewMapper: $parse(match[2] || match[1]),
7089 modelMapper: $parse(match[1])
7095 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'uibTypeaheadParser',
7096 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) {
7097 var HOT_KEYS = [9, 13, 27, 38, 40];
7098 var eventDebounceTime = 200;
7099 var modelCtrl, ngModelOptions;
7100 //SUPPORTED ATTRIBUTES (OPTIONS)
7102 //minimal no of characters that needs to be entered before typeahead kicks-in
7103 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
7104 if (!minLength && minLength !== 0) {
7108 //minimal wait time after last character typed before typeahead kicks-in
7109 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
7111 //should it restrict model values to the ones selected from the popup only?
7112 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
7114 //binding to a variable that indicates if matches are being retrieved asynchronously
7115 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
7117 //a callback executed when a match is selected
7118 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
7120 //should it select highlighted popup value when losing focus?
7121 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
7123 //binding to a variable that indicates if there were no results after the query is completed
7124 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
7126 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
7128 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
7130 var appendToElementId = attrs.typeaheadAppendToElementId || false;
7132 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
7134 //If input matches an item of the list exactly, select it automatically
7135 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
7137 //INTERNAL VARIABLES
7139 //model setter executed upon match selection
7140 var parsedModel = $parse(attrs.ngModel);
7141 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
7142 var $setModelValue = function(scope, newValue) {
7143 if (angular.isFunction(parsedModel(originalScope)) &&
7144 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
7145 return invokeModelSetter(scope, {$$$p: newValue});
7147 return parsedModel.assign(scope, newValue);
7151 //expressions used by typeahead
7152 var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
7156 //Used to avoid bug in iOS webview where iOS keyboard does not fire
7157 //mousedown & mouseup events
7161 //create a child scope for the typeahead directive so we are not polluting original scope
7162 //with typeahead-specific data (matches, query etc.)
7163 var scope = originalScope.$new();
7164 var offDestroy = originalScope.$on('$destroy', function() {
7167 scope.$on('$destroy', offDestroy);
7170 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
7172 'aria-autocomplete': 'list',
7173 'aria-expanded': false,
7174 'aria-owns': popupId
7177 //pop-up element used to display matches
7178 var popUpEl = angular.element('<div uib-typeahead-popup></div>');
7182 active: 'activeIdx',
7183 select: 'select(activeIdx)',
7184 'move-in-progress': 'moveInProgress',
7186 position: 'position'
7188 //custom item template
7189 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
7190 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
7193 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
7194 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
7197 var resetMatches = function() {
7199 scope.activeIdx = -1;
7200 element.attr('aria-expanded', false);
7203 var getMatchId = function(index) {
7204 return popupId + '-option-' + index;
7207 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
7208 // This attribute is added or removed automatically when the `activeIdx` changes.
7209 scope.$watch('activeIdx', function(index) {
7211 element.removeAttr('aria-activedescendant');
7213 element.attr('aria-activedescendant', getMatchId(index));
7217 var inputIsExactMatch = function(inputValue, index) {
7218 if (scope.matches.length > index && inputValue) {
7219 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
7225 var getMatchesAsync = function(inputValue) {
7226 var locals = {$viewValue: inputValue};
7227 isLoadingSetter(originalScope, true);
7228 isNoResultsSetter(originalScope, false);
7229 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
7230 //it might happen that several async queries were in progress if a user were typing fast
7231 //but we are interested only in responses that correspond to the current view value
7232 var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
7233 if (onCurrentRequest && hasFocus) {
7234 if (matches && matches.length > 0) {
7235 scope.activeIdx = focusFirst ? 0 : -1;
7236 isNoResultsSetter(originalScope, false);
7237 scope.matches.length = 0;
7240 for (var i = 0; i < matches.length; i++) {
7241 locals[parserResult.itemName] = matches[i];
7242 scope.matches.push({
7244 label: parserResult.viewMapper(scope, locals),
7249 scope.query = inputValue;
7250 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
7251 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
7252 //due to other elements being rendered
7253 recalculatePosition();
7255 element.attr('aria-expanded', true);
7257 //Select the single remaining option if user input matches
7258 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
7263 isNoResultsSetter(originalScope, true);
7266 if (onCurrentRequest) {
7267 isLoadingSetter(originalScope, false);
7271 isLoadingSetter(originalScope, false);
7272 isNoResultsSetter(originalScope, true);
7276 // bind events only if appendToBody params exist - performance feature
7278 angular.element($window).bind('resize', fireRecalculating);
7279 $document.find('body').bind('scroll', fireRecalculating);
7282 // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
7283 var timeoutEventPromise;
7285 // Default progress type
7286 scope.moveInProgress = false;
7288 function fireRecalculating() {
7289 if (!scope.moveInProgress) {
7290 scope.moveInProgress = true;
7294 // Cancel previous timeout
7295 if (timeoutEventPromise) {
7296 $timeout.cancel(timeoutEventPromise);
7299 // Debounced executing recalculate after events fired
7300 timeoutEventPromise = $timeout(function() {
7301 // if popup is visible
7302 if (scope.matches.length) {
7303 recalculatePosition();
7306 scope.moveInProgress = false;
7307 }, eventDebounceTime);
7310 // recalculate actual position and set new values to scope
7311 // after digest loop is popup in right position
7312 function recalculatePosition() {
7313 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
7314 scope.position.top += element.prop('offsetHeight');
7317 //we need to propagate user's query so we can higlight matches
7318 scope.query = undefined;
7320 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
7323 var scheduleSearchWithTimeout = function(inputValue) {
7324 timeoutPromise = $timeout(function() {
7325 getMatchesAsync(inputValue);
7329 var cancelPreviousTimeout = function() {
7330 if (timeoutPromise) {
7331 $timeout.cancel(timeoutPromise);
7337 scope.select = function(activeIdx) {
7338 //called from within the $digest() cycle
7343 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
7344 model = parserResult.modelMapper(originalScope, locals);
7345 $setModelValue(originalScope, model);
7346 modelCtrl.$setValidity('editable', true);
7347 modelCtrl.$setValidity('parse', true);
7349 onSelectCallback(originalScope, {
7352 $label: parserResult.viewMapper(originalScope, locals)
7357 //return focus to the input element if a match was selected via a mouse click event
7358 // use timeout to avoid $rootScope:inprog error
7359 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
7360 $timeout(function() { element[0].focus(); }, 0, false);
7364 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
7365 element.bind('keydown', function(evt) {
7366 //typeahead is open and an "interesting" key was pressed
7367 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
7371 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
7372 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
7378 evt.preventDefault();
7380 if (evt.which === 40) {
7381 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
7383 } else if (evt.which === 38) {
7384 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
7386 } else if (evt.which === 13 || evt.which === 9) {
7387 scope.$apply(function () {
7388 scope.select(scope.activeIdx);
7390 } else if (evt.which === 27) {
7391 evt.stopPropagation();
7398 element.bind('blur', function() {
7399 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
7401 scope.$apply(function() {
7402 scope.select(scope.activeIdx);
7409 // Keep reference to click handler to unbind it.
7410 var dismissClickHandler = function(evt) {
7412 // Firefox treats right click as a click on document
7413 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
7415 if (!$rootScope.$$phase) {
7421 $document.bind('click', dismissClickHandler);
7423 originalScope.$on('$destroy', function() {
7424 $document.unbind('click', dismissClickHandler);
7425 if (appendToBody || appendToElementId) {
7430 angular.element($window).unbind('resize', fireRecalculating);
7431 $document.find('body').unbind('scroll', fireRecalculating);
7433 // Prevent jQuery cache memory leak
7437 var $popup = $compile(popUpEl)(scope);
7440 $document.find('body').append($popup);
7441 } else if (appendToElementId !== false) {
7442 angular.element($document[0].getElementById(appendToElementId)).append($popup);
7444 element.after($popup);
7447 this.init = function(_modelCtrl, _ngModelOptions) {
7448 modelCtrl = _modelCtrl;
7449 ngModelOptions = _ngModelOptions;
7451 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
7452 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
7453 modelCtrl.$parsers.unshift(function(inputValue) {
7456 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
7458 cancelPreviousTimeout();
7459 scheduleSearchWithTimeout(inputValue);
7461 getMatchesAsync(inputValue);
7464 isLoadingSetter(originalScope, false);
7465 cancelPreviousTimeout();
7473 // Reset in case user had typed something previously.
7474 modelCtrl.$setValidity('editable', true);
7477 modelCtrl.$setValidity('editable', false);
7483 modelCtrl.$formatters.push(function(modelValue) {
7484 var candidateViewValue, emptyViewValue;
7487 // The validity may be set to false via $parsers (see above) if
7488 // the model is restricted to selected values. If the model
7489 // is set manually it is considered to be valid.
7491 modelCtrl.$setValidity('editable', true);
7494 if (inputFormatter) {
7495 locals.$model = modelValue;
7496 return inputFormatter(originalScope, locals);
7498 //it might happen that we don't have enough info to properly render input value
7499 //we need to check for this situation and simply return model value if we can't apply custom formatting
7500 locals[parserResult.itemName] = modelValue;
7501 candidateViewValue = parserResult.viewMapper(originalScope, locals);
7502 locals[parserResult.itemName] = undefined;
7503 emptyViewValue = parserResult.viewMapper(originalScope, locals);
7505 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
7511 .directive('uibTypeahead', function() {
7513 controller: 'UibTypeaheadController',
7514 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
7515 link: function(originalScope, element, attrs, ctrls) {
7516 ctrls[2].init(ctrls[0], ctrls[1]);
7521 .directive('uibTypeaheadPopup', function() {
7528 moveInProgress: '=',
7532 templateUrl: function(element, attrs) {
7533 return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html';
7535 link: function(scope, element, attrs) {
7536 scope.templateUrl = attrs.templateUrl;
7538 scope.isOpen = function() {
7539 return scope.matches.length > 0;
7542 scope.isActive = function(matchIdx) {
7543 return scope.active == matchIdx;
7546 scope.selectActive = function(matchIdx) {
7547 scope.active = matchIdx;
7550 scope.selectMatch = function(activeIdx) {
7551 scope.select({activeIdx:activeIdx});
7557 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
7564 link:function(scope, element, attrs) {
7565 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
7566 $templateRequest(tplUrl).then(function(tplContent) {
7567 $compile(tplContent.trim())(scope, function(clonedElement) {
7568 element.replaceWith(clonedElement);
7575 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
7576 var isSanitizePresent;
7577 isSanitizePresent = $injector.has('$sanitize');
7579 function escapeRegexp(queryToEscape) {
7580 // Regex: capture the whole query string and replace it with the string that will be used to match
7581 // the results, for example if the capture is "a" the result will be \a
7582 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
7585 function containsHtml(matchItem) {
7586 return /<.*>/g.test(matchItem);
7589 return function(matchItem, query) {
7590 if (!isSanitizePresent && containsHtml(matchItem)) {
7591 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
7593 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
7594 if (!isSanitizePresent) {
7595 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
7601 /* Deprecated typeahead below */
7603 angular.module('ui.bootstrap.typeahead')
7604 .value('$typeaheadSuppressWarning', false)
7605 .service('typeaheadParser', ['$parse', 'uibTypeaheadParser', '$log', '$typeaheadSuppressWarning', function($parse, uibTypeaheadParser, $log, $typeaheadSuppressWarning) {
7606 if (!$typeaheadSuppressWarning) {
7607 $log.warn('typeaheadParser is now deprecated. Use uibTypeaheadParser instead.');
7610 return uibTypeaheadParser;
7613 .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$uibPosition', 'typeaheadParser', '$log', '$typeaheadSuppressWarning',
7614 function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser, $log, $typeaheadSuppressWarning) {
7615 var HOT_KEYS = [9, 13, 27, 38, 40];
7616 var eventDebounceTime = 200;
7618 require: ['ngModel', '^?ngModelOptions'],
7619 link: function(originalScope, element, attrs, ctrls) {
7620 if (!$typeaheadSuppressWarning) {
7621 $log.warn('typeahead is now deprecated. Use uib-typeahead instead.');
7623 var modelCtrl = ctrls[0];
7624 var ngModelOptions = ctrls[1];
7625 //SUPPORTED ATTRIBUTES (OPTIONS)
7627 //minimal no of characters that needs to be entered before typeahead kicks-in
7628 var minLength = originalScope.$eval(attrs.typeaheadMinLength);
7629 if (!minLength && minLength !== 0) {
7633 //minimal wait time after last character typed before typeahead kicks-in
7634 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
7636 //should it restrict model values to the ones selected from the popup only?
7637 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
7639 //binding to a variable that indicates if matches are being retrieved asynchronously
7640 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
7642 //a callback executed when a match is selected
7643 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
7645 //should it select highlighted popup value when losing focus?
7646 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
7648 //binding to a variable that indicates if there were no results after the query is completed
7649 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
7651 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
7653 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
7655 var appendToElementId = attrs.typeaheadAppendToElementId || false;
7657 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
7659 //If input matches an item of the list exactly, select it automatically
7660 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
7662 //INTERNAL VARIABLES
7664 //model setter executed upon match selection
7665 var parsedModel = $parse(attrs.ngModel);
7666 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
7667 var $setModelValue = function(scope, newValue) {
7668 if (angular.isFunction(parsedModel(originalScope)) &&
7669 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
7670 return invokeModelSetter(scope, {$$$p: newValue});
7672 return parsedModel.assign(scope, newValue);
7676 //expressions used by typeahead
7677 var parserResult = typeaheadParser.parse(attrs.typeahead);
7681 //Used to avoid bug in iOS webview where iOS keyboard does not fire
7682 //mousedown & mouseup events
7686 //create a child scope for the typeahead directive so we are not polluting original scope
7687 //with typeahead-specific data (matches, query etc.)
7688 var scope = originalScope.$new();
7689 var offDestroy = originalScope.$on('$destroy', function() {
7692 scope.$on('$destroy', offDestroy);
7695 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
7697 'aria-autocomplete': 'list',
7698 'aria-expanded': false,
7699 'aria-owns': popupId
7702 //pop-up element used to display matches
7703 var popUpEl = angular.element('<div typeahead-popup></div>');
7707 active: 'activeIdx',
7708 select: 'select(activeIdx)',
7709 'move-in-progress': 'moveInProgress',
7711 position: 'position'
7713 //custom item template
7714 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
7715 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
7718 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
7719 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
7722 var resetMatches = function() {
7724 scope.activeIdx = -1;
7725 element.attr('aria-expanded', false);
7728 var getMatchId = function(index) {
7729 return popupId + '-option-' + index;
7732 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
7733 // This attribute is added or removed automatically when the `activeIdx` changes.
7734 scope.$watch('activeIdx', function(index) {
7736 element.removeAttr('aria-activedescendant');
7738 element.attr('aria-activedescendant', getMatchId(index));
7742 var inputIsExactMatch = function(inputValue, index) {
7743 if (scope.matches.length > index && inputValue) {
7744 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
7750 var getMatchesAsync = function(inputValue) {
7751 var locals = {$viewValue: inputValue};
7752 isLoadingSetter(originalScope, true);
7753 isNoResultsSetter(originalScope, false);
7754 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
7755 //it might happen that several async queries were in progress if a user were typing fast
7756 //but we are interested only in responses that correspond to the current view value
7757 var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
7758 if (onCurrentRequest && hasFocus) {
7759 if (matches && matches.length > 0) {
7760 scope.activeIdx = focusFirst ? 0 : -1;
7761 isNoResultsSetter(originalScope, false);
7762 scope.matches.length = 0;
7765 for (var i = 0; i < matches.length; i++) {
7766 locals[parserResult.itemName] = matches[i];
7767 scope.matches.push({
7769 label: parserResult.viewMapper(scope, locals),
7774 scope.query = inputValue;
7775 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
7776 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
7777 //due to other elements being rendered
7778 recalculatePosition();
7780 element.attr('aria-expanded', true);
7782 //Select the single remaining option if user input matches
7783 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
7788 isNoResultsSetter(originalScope, true);
7791 if (onCurrentRequest) {
7792 isLoadingSetter(originalScope, false);
7796 isLoadingSetter(originalScope, false);
7797 isNoResultsSetter(originalScope, true);
7801 // bind events only if appendToBody params exist - performance feature
7803 angular.element($window).bind('resize', fireRecalculating);
7804 $document.find('body').bind('scroll', fireRecalculating);
7807 // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
7808 var timeoutEventPromise;
7810 // Default progress type
7811 scope.moveInProgress = false;
7813 function fireRecalculating() {
7814 if (!scope.moveInProgress) {
7815 scope.moveInProgress = true;
7819 // Cancel previous timeout
7820 if (timeoutEventPromise) {
7821 $timeout.cancel(timeoutEventPromise);
7824 // Debounced executing recalculate after events fired
7825 timeoutEventPromise = $timeout(function() {
7826 // if popup is visible
7827 if (scope.matches.length) {
7828 recalculatePosition();
7831 scope.moveInProgress = false;
7832 }, eventDebounceTime);
7835 // recalculate actual position and set new values to scope
7836 // after digest loop is popup in right position
7837 function recalculatePosition() {
7838 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
7839 scope.position.top += element.prop('offsetHeight');
7844 //we need to propagate user's query so we can higlight matches
7845 scope.query = undefined;
7847 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
7850 var scheduleSearchWithTimeout = function(inputValue) {
7851 timeoutPromise = $timeout(function() {
7852 getMatchesAsync(inputValue);
7856 var cancelPreviousTimeout = function() {
7857 if (timeoutPromise) {
7858 $timeout.cancel(timeoutPromise);
7862 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
7863 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
7864 modelCtrl.$parsers.unshift(function(inputValue) {
7867 if (minLength === 0 || inputValue && inputValue.length >= minLength) {
7869 cancelPreviousTimeout();
7870 scheduleSearchWithTimeout(inputValue);
7872 getMatchesAsync(inputValue);
7875 isLoadingSetter(originalScope, false);
7876 cancelPreviousTimeout();
7884 // Reset in case user had typed something previously.
7885 modelCtrl.$setValidity('editable', true);
7888 modelCtrl.$setValidity('editable', false);
7894 modelCtrl.$formatters.push(function(modelValue) {
7895 var candidateViewValue, emptyViewValue;
7898 // The validity may be set to false via $parsers (see above) if
7899 // the model is restricted to selected values. If the model
7900 // is set manually it is considered to be valid.
7902 modelCtrl.$setValidity('editable', true);
7905 if (inputFormatter) {
7906 locals.$model = modelValue;
7907 return inputFormatter(originalScope, locals);
7909 //it might happen that we don't have enough info to properly render input value
7910 //we need to check for this situation and simply return model value if we can't apply custom formatting
7911 locals[parserResult.itemName] = modelValue;
7912 candidateViewValue = parserResult.viewMapper(originalScope, locals);
7913 locals[parserResult.itemName] = undefined;
7914 emptyViewValue = parserResult.viewMapper(originalScope, locals);
7916 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
7920 scope.select = function(activeIdx) {
7921 //called from within the $digest() cycle
7926 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
7927 model = parserResult.modelMapper(originalScope, locals);
7928 $setModelValue(originalScope, model);
7929 modelCtrl.$setValidity('editable', true);
7930 modelCtrl.$setValidity('parse', true);
7932 onSelectCallback(originalScope, {
7935 $label: parserResult.viewMapper(originalScope, locals)
7940 //return focus to the input element if a match was selected via a mouse click event
7941 // use timeout to avoid $rootScope:inprog error
7942 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
7943 $timeout(function() { element[0].focus(); }, 0, false);
7947 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
7948 element.bind('keydown', function(evt) {
7949 //typeahead is open and an "interesting" key was pressed
7950 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
7954 // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
7955 if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
7961 evt.preventDefault();
7963 if (evt.which === 40) {
7964 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
7966 } else if (evt.which === 38) {
7967 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
7969 } else if (evt.which === 13 || evt.which === 9) {
7970 scope.$apply(function () {
7971 scope.select(scope.activeIdx);
7973 } else if (evt.which === 27) {
7974 evt.stopPropagation();
7981 element.bind('blur', function() {
7982 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
7984 scope.$apply(function() {
7985 scope.select(scope.activeIdx);
7992 // Keep reference to click handler to unbind it.
7993 var dismissClickHandler = function(evt) {
7995 // Firefox treats right click as a click on document
7996 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
7998 if (!$rootScope.$$phase) {
8004 $document.bind('click', dismissClickHandler);
8006 originalScope.$on('$destroy', function() {
8007 $document.unbind('click', dismissClickHandler);
8008 if (appendToBody || appendToElementId) {
8013 angular.element($window).unbind('resize', fireRecalculating);
8014 $document.find('body').unbind('scroll', fireRecalculating);
8016 // Prevent jQuery cache memory leak
8020 var $popup = $compile(popUpEl)(scope);
8023 $document.find('body').append($popup);
8024 } else if (appendToElementId !== false) {
8025 angular.element($document[0].getElementById(appendToElementId)).append($popup);
8027 element.after($popup);
8033 .directive('typeaheadPopup', ['$typeaheadSuppressWarning', '$log', function($typeaheadSuppressWarning, $log) {
8040 moveInProgress: '=',
8044 templateUrl: function(element, attrs) {
8045 return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html';
8047 link: function(scope, element, attrs) {
8049 if (!$typeaheadSuppressWarning) {
8050 $log.warn('typeahead-popup is now deprecated. Use uib-typeahead-popup instead.');
8052 scope.templateUrl = attrs.templateUrl;
8054 scope.isOpen = function() {
8055 return scope.matches.length > 0;
8058 scope.isActive = function(matchIdx) {
8059 return scope.active == matchIdx;
8062 scope.selectActive = function(matchIdx) {
8063 scope.active = matchIdx;
8066 scope.selectMatch = function(activeIdx) {
8067 scope.select({activeIdx:activeIdx});
8073 .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', '$typeaheadSuppressWarning', '$log', function($templateRequest, $compile, $parse, $typeaheadSuppressWarning, $log) {
8081 link:function(scope, element, attrs) {
8082 if (!$typeaheadSuppressWarning) {
8083 $log.warn('typeahead-match is now deprecated. Use uib-typeahead-match instead.');
8086 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
8087 $templateRequest(tplUrl).then(function(tplContent) {
8088 $compile(tplContent.trim())(scope, function(clonedElement) {
8089 element.replaceWith(clonedElement);
8096 .filter('typeaheadHighlight', ['$sce', '$injector', '$log', '$typeaheadSuppressWarning', function($sce, $injector, $log, $typeaheadSuppressWarning) {
8097 var isSanitizePresent;
8098 isSanitizePresent = $injector.has('$sanitize');
8100 function escapeRegexp(queryToEscape) {
8101 // Regex: capture the whole query string and replace it with the string that will be used to match
8102 // the results, for example if the capture is "a" the result will be \a
8103 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
8106 function containsHtml(matchItem) {
8107 return /<.*>/g.test(matchItem);
8110 return function(matchItem, query) {
8111 if (!$typeaheadSuppressWarning) {
8112 $log.warn('typeaheadHighlight is now deprecated. Use uibTypeaheadHighlight instead.');
8115 if (!isSanitizePresent && containsHtml(matchItem)) {
8116 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
8119 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
8120 if (!isSanitizePresent) {
8121 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
8128 angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
8129 $templateCache.put("template/accordion/accordion-group.html",
8130 "<div class=\"panel {{panelClass || 'panel-default'}}\">\n" +
8131 " <div class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" +
8132 " <h4 class=\"panel-title\">\n" +
8133 " <a href tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
8136 " <div class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" +
8137 " <div class=\"panel-body\" ng-transclude></div>\n" +
8143 angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) {
8144 $templateCache.put("template/accordion/accordion.html",
8145 "<div class=\"panel-group\" ng-transclude></div>");
8148 angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
8149 $templateCache.put("template/alert/alert.html",
8150 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" +
8151 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" +
8152 " <span aria-hidden=\"true\">×</span>\n" +
8153 " <span class=\"sr-only\">Close</span>\n" +
8155 " <div ng-transclude></div>\n" +
8160 angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
8161 $templateCache.put("template/carousel/carousel.html",
8162 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
8163 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
8164 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\">\n" +
8165 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" +
8166 " <span class=\"sr-only\">previous</span>\n" +
8168 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\">\n" +
8169 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" +
8170 " <span class=\"sr-only\">next</span>\n" +
8172 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
8173 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" +
8174 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" +
8180 angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) {
8181 $templateCache.put("template/carousel/slide.html",
8182 "<div ng-class=\"{\n" +
8183 " 'active': active\n" +
8184 " }\" class=\"item text-center\" ng-transclude></div>\n" +
8188 angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
8189 $templateCache.put("template/datepicker/datepicker.html",
8190 "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
8191 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" +
8192 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" +
8193 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" +
8197 angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
8198 $templateCache.put("template/datepicker/day.html",
8199 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
8202 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
8203 " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
8204 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
8207 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" +
8208 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" +
8212 " <tr ng-repeat=\"row in rows track by $index\">\n" +
8213 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
8214 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\" ng-class=\"::dt.customClass\">\n" +
8215 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
8223 angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
8224 $templateCache.put("template/datepicker/month.html",
8225 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
8228 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
8229 " <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
8230 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
8234 " <tr ng-repeat=\"row in rows track by $index\">\n" +
8235 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\" ng-class=\"::dt.customClass\">\n" +
8236 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
8244 angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
8245 $templateCache.put("template/datepicker/popup.html",
8246 "<ul class=\"dropdown-menu\" dropdown-nested ng-if=\"isOpen\" style=\"display: block\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" +
8247 " <li ng-transclude></li>\n" +
8248 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
8249 " <span class=\"btn-group pull-left\">\n" +
8250 " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" +
8251 " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
8253 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
8259 angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
8260 $templateCache.put("template/datepicker/year.html",
8261 "<table role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
8264 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
8265 " <th colspan=\"3\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
8266 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
8270 " <tr ng-repeat=\"row in rows track by $index\">\n" +
8271 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{::dt.uid}}\" ng-class=\"::dt.customClass\">\n" +
8272 " <button type=\"button\" style=\"min-width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" +
8280 angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
8281 $templateCache.put("template/modal/backdrop.html",
8282 "<div uib-modal-animation-class=\"fade\"\n" +
8283 " modal-in-class=\"in\"\n" +
8284 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
8289 angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
8290 $templateCache.put("template/modal/window.html",
8291 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" +
8292 " uib-modal-animation-class=\"fade\"\n" +
8293 " modal-in-class=\"in\"\n" +
8294 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" +
8295 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" +
8300 angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
8301 $templateCache.put("template/pagination/pager.html",
8302 "<ul class=\"pager\">\n" +
8303 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
8304 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
8309 angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
8310 $templateCache.put("template/pagination/pagination.html",
8311 "<ul class=\"pagination\">\n" +
8312 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" +
8313 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" +
8314 " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\">{{page.text}}</a></li>\n" +
8315 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" +
8316 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" +
8321 angular.module("template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) {
8322 $templateCache.put("template/tooltip/tooltip-html-popup.html",
8324 " tooltip-animation-class=\"fade\"\n" +
8325 " uib-tooltip-classes\n" +
8326 " ng-class=\"{ in: isOpen() }\">\n" +
8327 " <div class=\"tooltip-arrow\"></div>\n" +
8328 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" +
8333 angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) {
8334 $templateCache.put("template/tooltip/tooltip-popup.html",
8336 " tooltip-animation-class=\"fade\"\n" +
8337 " uib-tooltip-classes\n" +
8338 " ng-class=\"{ in: isOpen() }\">\n" +
8339 " <div class=\"tooltip-arrow\"></div>\n" +
8340 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
8345 angular.module("template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) {
8346 $templateCache.put("template/tooltip/tooltip-template-popup.html",
8348 " tooltip-animation-class=\"fade\"\n" +
8349 " uib-tooltip-classes\n" +
8350 " ng-class=\"{ in: isOpen() }\">\n" +
8351 " <div class=\"tooltip-arrow\"></div>\n" +
8352 " <div class=\"tooltip-inner\"\n" +
8353 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
8354 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
8359 angular.module("template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) {
8360 $templateCache.put("template/popover/popover-html.html",
8361 "<div tooltip-animation-class=\"fade\"\n" +
8362 " uib-tooltip-classes\n" +
8363 " ng-class=\"{ in: isOpen() }\">\n" +
8364 " <div class=\"arrow\"></div>\n" +
8366 " <div class=\"popover-inner\">\n" +
8367 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
8368 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" +
8374 angular.module("template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) {
8375 $templateCache.put("template/popover/popover-template.html",
8376 "<div tooltip-animation-class=\"fade\"\n" +
8377 " uib-tooltip-classes\n" +
8378 " ng-class=\"{ in: isOpen() }\">\n" +
8379 " <div class=\"arrow\"></div>\n" +
8381 " <div class=\"popover-inner\">\n" +
8382 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
8383 " <div class=\"popover-content\"\n" +
8384 " uib-tooltip-template-transclude=\"contentExp()\"\n" +
8385 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" +
8391 angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) {
8392 $templateCache.put("template/popover/popover.html",
8393 "<div tooltip-animation-class=\"fade\"\n" +
8394 " uib-tooltip-classes\n" +
8395 " ng-class=\"{ in: isOpen() }\">\n" +
8396 " <div class=\"arrow\"></div>\n" +
8398 " <div class=\"popover-inner\">\n" +
8399 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" +
8400 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
8406 angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
8407 $templateCache.put("template/progressbar/bar.html",
8408 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" style=\"min-width: 0;\" ng-transclude></div>\n" +
8412 angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
8413 $templateCache.put("template/progressbar/progress.html",
8414 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>");
8417 angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
8418 $templateCache.put("template/progressbar/progressbar.html",
8419 "<div class=\"progress\">\n" +
8420 " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" style=\"min-width: 0;\" ng-transclude></div>\n" +
8425 angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
8426 $templateCache.put("template/rating/rating.html",
8427 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
8428 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
8429 " <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\" aria-valuetext=\"{{r.title}}\"></i>\n" +
8434 angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) {
8435 $templateCache.put("template/tabs/tab.html",
8436 "<li ng-class=\"{active: active, disabled: disabled}\">\n" +
8437 " <a href ng-click=\"select()\" uib-tab-heading-transclude>{{heading}}</a>\n" +
8442 angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
8443 $templateCache.put("template/tabs/tabset.html",
8445 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
8446 " <div class=\"tab-content\">\n" +
8447 " <div class=\"tab-pane\" \n" +
8448 " ng-repeat=\"tab in tabs\" \n" +
8449 " ng-class=\"{active: tab.active}\"\n" +
8450 " uib-tab-content-transclude=\"tab\">\n" +
8457 angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
8458 $templateCache.put("template/timepicker/timepicker.html",
8461 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
8462 " <td><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
8463 " <td> </td>\n" +
8464 " <td><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
8465 " <td ng-show=\"showMeridian\"></td>\n" +
8468 " <td class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" +
8469 " <input style=\"width:50px;\" type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\">\n" +
8472 " <td class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
8473 " <input style=\"width:50px;\" type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\">\n" +
8475 " <td ng-show=\"showMeridian\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" +
8477 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" +
8478 " <td><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
8479 " <td> </td>\n" +
8480 " <td><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
8481 " <td ng-show=\"showMeridian\"></td>\n" +
8488 angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) {
8489 $templateCache.put("template/typeahead/typeahead-match.html",
8490 "<a href tabindex=\"-1\" ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"></a>\n" +
8494 angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
8495 $templateCache.put("template/typeahead/typeahead-popup.html",
8496 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
8497 " <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{::match.id}}\">\n" +
8498 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
8503 !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>');