2 * Created by aditya on 6/13/14.
4 angular
.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","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"]);
5 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/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.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"]);
6 angular
.module('ui.bootstrap.transition', [])
9 * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
10 * @param {DOMElement} element The DOMElement that will be animated.
11 * @param {string|object|function} trigger The thing that will cause the transition to start:
12 * - As a string, it represents the css class to be added to the element.
13 * - As an object, it represents a hash of style attributes to be applied to the element.
14 * - As a function, it represents a function to be called that will cause the transition to occur.
15 * @return {Promise} A promise that is resolved when the transition finishes.
17 .factory('$transition', ['$q', '$timeout', '$rootScope', function($q
, $timeout
, $rootScope
) {
19 var $transition = function(element
, trigger
, options
) {
20 options
= options
|| {};
21 var deferred
= $q
.defer();
22 var endEventName
= $transition
[options
.animation
? "animationEndEventName" : "transitionEndEventName"];
24 var transitionEndHandler = function(event
) {
25 $rootScope
.$apply(function() {
26 element
.unbind(endEventName
, transitionEndHandler
);
27 deferred
.resolve(element
);
32 element
.bind(endEventName
, transitionEndHandler
);
35 // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
37 if ( angular
.isString(trigger
) ) {
38 element
.addClass(trigger
);
39 } else if ( angular
.isFunction(trigger
) ) {
41 } else if ( angular
.isObject(trigger
) ) {
44 //If browser does not support transitions, instantly resolve
45 if ( !endEventName
) {
46 deferred
.resolve(element
);
50 // Add our custom cancel function to the promise that is returned
51 // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
52 // i.e. it will therefore never raise a transitionEnd event for that transition
53 deferred
.promise
.cancel = function() {
55 element
.unbind(endEventName
, transitionEndHandler
);
57 deferred
.reject('Transition cancelled');
60 return deferred
.promise
;
63 // Work out the name of the transitionEnd event
64 var transElement
= document
.createElement('trans');
65 var transitionEndEventNames
= {
66 'WebkitTransition': 'webkitTransitionEnd',
67 'MozTransition': 'transitionend',
68 'OTransition': 'oTransitionEnd',
69 'transition': 'transitionend'
71 var animationEndEventNames
= {
72 'WebkitTransition': 'webkitAnimationEnd',
73 'MozTransition': 'animationend',
74 'OTransition': 'oAnimationEnd',
75 'transition': 'animationend'
77 function findEndEventName(endEventNames
) {
78 for (var name
in endEventNames
){
79 if (transElement
.style
[name
] !== undefined) {
80 return endEventNames
[name
];
84 $transition
.transitionEndEventName
= findEndEventName(transitionEndEventNames
);
85 $transition
.animationEndEventName
= findEndEventName(animationEndEventNames
);
89 angular
.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
91 .directive('collapse', ['$transition', function ($transition
, $timeout
) {
94 link: function (scope
, element
, attrs
) {
96 var initialAnimSkip
= true;
97 var currentTransition
;
99 function doTransition(change
) {
100 var newTransition
= $transition(element
, change
);
101 if (currentTransition
) {
102 currentTransition
.cancel();
104 currentTransition
= newTransition
;
105 newTransition
.then(newTransitionDone
, newTransitionDone
);
106 return newTransition
;
108 function newTransitionDone() {
109 // Make sure it's this transition, otherwise, leave it alone.
110 if (currentTransition
=== newTransition
) {
111 currentTransition
= undefined;
117 if (initialAnimSkip
) {
118 initialAnimSkip
= false;
121 element
.removeClass('collapse').addClass('collapsing');
122 doTransition({ height
: element
[0].scrollHeight
+ 'px' }).then(expandDone
);
126 function expandDone() {
127 element
.removeClass('collapsing');
128 element
.addClass('collapse in');
129 element
.css({height
: 'auto'});
132 function collapse() {
133 if (initialAnimSkip
) {
134 initialAnimSkip
= false;
136 element
.css({height
: 0});
138 // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
139 element
.css({ height
: element
[0].scrollHeight
+ 'px' });
140 //trigger reflow so a browser relaizes that height was updated from auto to a specific value
141 var x
= element
[0].offsetWidth
;
143 element
.removeClass('collapse in').addClass('collapsing');
145 doTransition({ height
: 0 }).then(collapseDone
);
149 function collapseDone() {
150 element
.removeClass('collapsing');
151 element
.addClass('collapse');
154 scope
.$watch(attrs
.collapse
, function (shouldCollapse
) {
155 if (shouldCollapse
) {
165 angular
.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
167 .constant('accordionConfig', {
171 .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope
, $attrs
, accordionConfig
) {
173 // This array keeps track of the accordion groups
176 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
177 this.closeOthers = function(openGroup
) {
178 var closeOthers
= angular
.isDefined($attrs
.closeOthers
) ? $scope
.$eval($attrs
.closeOthers
) : accordionConfig
.closeOthers
;
180 angular
.forEach(this.groups
, function (group
) {
181 if ( group
!== openGroup
) {
182 group
.isOpen
= false;
188 // This is called from the accordion-group directive to add itself to the accordion
189 this.addGroup = function(groupScope
) {
191 this.groups
.push(groupScope
);
193 groupScope
.$on('$destroy', function (event
) {
194 that
.removeGroup(groupScope
);
198 // This is called from the accordion-group directive when to remove itself
199 this.removeGroup = function(group
) {
200 var index
= this.groups
.indexOf(group
);
201 if ( index
!== -1 ) {
202 this.groups
.splice(this.groups
.indexOf(group
), 1);
208 // The accordion directive simply sets up the directive controller
209 // and adds an accordion CSS class to itself element.
210 .directive('accordion', function () {
213 controller
:'AccordionController',
216 templateUrl
: 'template/accordion/accordion.html'
220 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
221 .directive('accordionGroup', ['$parse', function($parse
) {
223 require
:'^accordion', // We need this directive to be inside an accordion
225 transclude
:true, // It transcludes the contents of the directive into the template
226 replace
: true, // The element containing the directive will be replaced with the template
227 templateUrl
:'template/accordion/accordion-group.html',
228 scope
:{ heading
:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
229 controller: function() {
230 this.setHeading = function(element
) {
231 this.heading
= element
;
234 link: function(scope
, element
, attrs
, accordionCtrl
) {
235 var getIsOpen
, setIsOpen
;
237 accordionCtrl
.addGroup(scope
);
239 scope
.isOpen
= false;
241 if ( attrs
.isOpen
) {
242 getIsOpen
= $parse(attrs
.isOpen
);
243 setIsOpen
= getIsOpen
.assign
;
245 scope
.$parent
.$watch(getIsOpen
, function(value
) {
246 scope
.isOpen
= !!value
;
250 scope
.$watch('isOpen', function(value
) {
252 accordionCtrl
.closeOthers(scope
);
255 setIsOpen(scope
.$parent
, value
);
262 // Use accordion-heading below an accordion-group to provide a heading containing HTML
264 // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
265 // </accordion-group>
266 .directive('accordionHeading', function() {
269 transclude
: true, // Grab the contents to be used as the heading
270 template
: '', // In effect remove this element!
272 require
: '^accordionGroup',
273 compile: function(element
, attr
, transclude
) {
274 return function link(scope
, element
, attr
, accordionGroupCtrl
) {
275 // Pass the heading to the accordion-group controller
276 // so that it can be transcluded into the right place in the template
277 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
278 accordionGroupCtrl
.setHeading(transclude(scope
, function() {}));
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 // <div class="accordion-group">
287 // <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
290 .directive('accordionTransclude', function() {
292 require
: '^accordionGroup',
293 link: function(scope
, element
, attr
, controller
) {
294 scope
.$watch(function() { return controller
[attr
.accordionTransclude
]; }, function(heading
) {
297 element
.append(heading
);
304 angular
.module("ui.bootstrap.alert", [])
306 .controller('AlertController', ['$scope', '$attrs', function ($scope
, $attrs
) {
307 $scope
.closeable
= 'close' in $attrs
;
310 .directive('alert', function () {
313 controller
:'AlertController',
314 templateUrl
:'template/alert/alert.html',
324 angular
.module('ui.bootstrap.bindHtml', [])
326 .directive('bindHtmlUnsafe', function () {
327 return function (scope
, element
, attr
) {
328 element
.addClass('ng-binding').data('$binding', attr
.bindHtmlUnsafe
);
329 scope
.$watch(attr
.bindHtmlUnsafe
, function bindHtmlUnsafeWatchAction(value
) {
330 element
.html(value
|| '');
334 angular
.module('ui.bootstrap.buttons', [])
336 .constant('buttonConfig', {
337 activeClass
: 'active',
341 .controller('ButtonsController', ['buttonConfig', function(buttonConfig
) {
342 this.activeClass
= buttonConfig
.activeClass
|| 'active';
343 this.toggleEvent
= buttonConfig
.toggleEvent
|| 'click';
346 .directive('btnRadio', function () {
348 require
: ['btnRadio', 'ngModel'],
349 controller
: 'ButtonsController',
350 link: function (scope
, element
, attrs
, ctrls
) {
351 var buttonsCtrl
= ctrls
[0], ngModelCtrl
= ctrls
[1];
354 ngModelCtrl
.$render = function () {
355 element
.toggleClass(buttonsCtrl
.activeClass
, angular
.equals(ngModelCtrl
.$modelValue
, scope
.$eval(attrs
.btnRadio
)));
359 element
.bind(buttonsCtrl
.toggleEvent
, function () {
360 if (!element
.hasClass(buttonsCtrl
.activeClass
)) {
361 scope
.$apply(function () {
362 ngModelCtrl
.$setViewValue(scope
.$eval(attrs
.btnRadio
));
363 ngModelCtrl
.$render();
371 .directive('btnCheckbox', function () {
373 require
: ['btnCheckbox', 'ngModel'],
374 controller
: 'ButtonsController',
375 link: function (scope
, element
, attrs
, ctrls
) {
376 var buttonsCtrl
= ctrls
[0], ngModelCtrl
= ctrls
[1];
378 function getTrueValue() {
379 return getCheckboxValue(attrs
.btnCheckboxTrue
, true);
382 function getFalseValue() {
383 return getCheckboxValue(attrs
.btnCheckboxFalse
, false);
386 function getCheckboxValue(attributeValue
, defaultValue
) {
387 var val
= scope
.$eval(attributeValue
);
388 return angular
.isDefined(val
) ? val
: defaultValue
;
392 ngModelCtrl
.$render = function () {
393 element
.toggleClass(buttonsCtrl
.activeClass
, angular
.equals(ngModelCtrl
.$modelValue
, getTrueValue()));
397 element
.bind(buttonsCtrl
.toggleEvent
, function () {
398 scope
.$apply(function () {
399 ngModelCtrl
.$setViewValue(element
.hasClass(buttonsCtrl
.activeClass
) ? getFalseValue() : getTrueValue());
400 ngModelCtrl
.$render();
409 * @name ui.bootstrap.carousel
412 * AngularJS version of an image carousel.
415 angular
.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
416 .controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope
, $timeout
, $transition
, $q
) {
418 slides
= self
.slides
= [],
420 currentTimeout
, isPlaying
;
421 self
.currentSlide
= null;
423 var destroyed
= false;
424 /* direction: "prev" or "next" */
425 self
.select = function(nextSlide
, direction
) {
426 var nextIndex
= slides
.indexOf(nextSlide
);
427 //Decide direction if it's not given
428 if (direction
=== undefined) {
429 direction
= nextIndex
> currentIndex
? "next" : "prev";
431 if (nextSlide
&& nextSlide
!== self
.currentSlide
) {
432 if ($scope
.$currentTransition
) {
433 $scope
.$currentTransition
.cancel();
434 //Timeout so ng-class in template has time to fix classes for finished slide
441 // Scope has been destroyed, stop here.
442 if (destroyed
) { return; }
443 //If we have a slide to transition from and we have a transition type and we're allowed, go
444 if (self
.currentSlide
&& angular
.isString(direction
) && !$scope
.noTransition
&& nextSlide
.$element
) {
445 //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
446 nextSlide
.$element
.addClass(direction
);
447 var reflow
= nextSlide
.$element
[0].offsetWidth
; //force reflow
449 //Set all other slides to stop doing their stuff for the new transition
450 angular
.forEach(slides
, function(slide
) {
451 angular
.extend(slide
, {direction
: '', entering
: false, leaving
: false, active
: false});
453 angular
.extend(nextSlide
, {direction
: direction
, active
: true, entering
: true});
454 angular
.extend(self
.currentSlide
||{}, {direction
: direction
, leaving
: true});
456 $scope
.$currentTransition
= $transition(nextSlide
.$element
, {});
457 //We have to create new pointers inside a closure since next & current will change
458 (function(next
,current
) {
459 $scope
.$currentTransition
.then(
460 function(){ transitionDone(next
, current
); },
461 function(){ transitionDone(next
, current
); }
463 }(nextSlide
, self
.currentSlide
));
465 transitionDone(nextSlide
, self
.currentSlide
);
467 self
.currentSlide
= nextSlide
;
468 currentIndex
= nextIndex
;
469 //every time you change slides, reset the timer
472 function transitionDone(next
, current
) {
473 angular
.extend(next
, {direction
: '', active
: true, leaving
: false, entering
: false});
474 angular
.extend(current
||{}, {direction
: '', active
: false, leaving
: false, entering
: false});
475 $scope
.$currentTransition
= null;
478 $scope
.$on('$destroy', function () {
482 /* Allow outside people to call indexOf on slides array */
483 self
.indexOfSlide = function(slide
) {
484 return slides
.indexOf(slide
);
487 $scope
.next = function() {
488 var newIndex
= (currentIndex
+ 1) % slides
.length
;
490 //Prevent this user-triggered transition from occurring if there is already one in progress
491 if (!$scope
.$currentTransition
) {
492 return self
.select(slides
[newIndex
], 'next');
496 $scope
.prev = function() {
497 var newIndex
= currentIndex
- 1 < 0 ? slides
.length
- 1 : currentIndex
- 1;
499 //Prevent this user-triggered transition from occurring if there is already one in progress
500 if (!$scope
.$currentTransition
) {
501 return self
.select(slides
[newIndex
], 'prev');
505 $scope
.select = function(slide
) {
509 $scope
.isActive = function(slide
) {
510 return self
.currentSlide
=== slide
;
513 $scope
.slides = function() {
517 $scope
.$watch('interval', restartTimer
);
518 $scope
.$on('$destroy', resetTimer
);
520 function restartTimer() {
522 var interval
= +$scope
.interval
;
523 if (!isNaN(interval
) && interval
>=0) {
524 currentTimeout
= $timeout(timerFn
, interval
);
528 function resetTimer() {
529 if (currentTimeout
) {
530 $timeout
.cancel(currentTimeout
);
531 currentTimeout
= null;
544 $scope
.play = function() {
550 $scope
.pause = function() {
551 if (!$scope
.noPause
) {
557 self
.addSlide = function(slide
, element
) {
558 slide
.$element
= element
;
560 //if this is the first slide or the slide is set to active, select it
561 if(slides
.length
=== 1 || slide
.active
) {
562 self
.select(slides
[slides
.length
-1]);
563 if (slides
.length
== 1) {
567 slide
.active
= false;
571 self
.removeSlide = function(slide
) {
572 //get the index of the slide inside the carousel
573 var index
= slides
.indexOf(slide
);
574 slides
.splice(index
, 1);
575 if (slides
.length
> 0 && slide
.active
) {
576 if (index
>= slides
.length
) {
577 self
.select(slides
[index
-1]);
579 self
.select(slides
[index
]);
581 } else if (currentIndex
> index
) {
590 * @name ui.bootstrap.carousel.directive:carousel
594 * Carousel is the outer container for a set of image 'slides' to showcase.
596 * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
597 * @param {boolean=} noTransition Whether to disable transitions on the carousel.
598 * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
601 <example module="ui.bootstrap">
602 <file name="index.html">
605 <img src="http://placekitten.com/150/150" style="margin:auto;">
606 <div class="carousel-caption">
611 <img src="http://placekitten.com/100/150" style="margin:auto;">
612 <div class="carousel-caption">
618 <file name="demo.css">
619 .carousel-indicators {
626 .directive('carousel', [function() {
631 controller
: 'CarouselController',
633 templateUrl
: 'template/carousel/carousel.html',
644 * @name ui.bootstrap.carousel.directive:slide
648 * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
650 * @param {boolean=} active Model binding, whether or not this slide is currently active.
653 <example module="ui.bootstrap">
654 <file name="index.html">
655 <div ng-controller="CarouselDemoCtrl">
657 <slide ng-repeat="slide in slides" active="slide.active">
658 <img ng-src="{{slide.image}}" style="margin:auto;">
659 <div class="carousel-caption">
660 <h4>Slide {{$index}}</h4>
661 <p>{{slide.text}}</p>
665 <div class="row-fluid">
668 <li ng-repeat="slide in slides">
669 <button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
670 {{$index}}: {{slide.text}}
673 <a class="btn" ng-click="addSlide()">Add Slide</a>
676 Interval, in milliseconds: <input type="number" ng-model="myInterval">
677 <br />Enter a negative number to stop the interval.
682 <file name="script.js">
683 function CarouselDemoCtrl($scope) {
684 $scope.myInterval = 5000;
685 var slides = $scope.slides = [];
686 $scope.addSlide = function() {
687 var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
689 image: 'http://placekitten.com/' + newWidth + '/200',
690 text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
691 ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
694 for (var i=0; i<4; i++) $scope.addSlide();
697 <file name="demo.css">
698 .carousel-indicators {
706 .directive('slide', ['$parse', function($parse
) {
708 require
: '^carousel',
712 templateUrl
: 'template/carousel/slide.html',
715 link: function (scope
, element
, attrs
, carouselCtrl
) {
716 //Set up optional 'active' = binding
718 var getActive
= $parse(attrs
.active
);
719 var setActive
= getActive
.assign
;
720 var lastValue
= scope
.active
= getActive(scope
.$parent
);
721 scope
.$watch(function parentActiveWatch() {
722 var parentActive
= getActive(scope
.$parent
);
724 if (parentActive
!== scope
.active
) {
725 // we are out of sync and need to copy
726 if (parentActive
!== lastValue
) {
727 // parent changed and it has precedence
728 lastValue
= scope
.active
= parentActive
;
730 // if the parent can be assigned then do so
731 setActive(scope
.$parent
, parentActive
= lastValue
= scope
.active
);
738 carouselCtrl
.addSlide(scope
, element
);
739 //when the scope is destroyed then remove the slide from the current slides array
740 scope
.$on('$destroy', function() {
741 carouselCtrl
.removeSlide(scope
);
744 scope
.$watch('active', function(active
) {
746 carouselCtrl
.select(scope
);
753 angular
.module('ui.bootstrap.position', [])
756 * A set of utility methods that can be use to retrieve position of DOM elements.
757 * It is meant to be used where we need to absolute-position DOM elements in
758 * relation to other, existing elements (this is the case for tooltips, popovers,
759 * typeahead suggestions etc.).
761 .factory('$position', ['$document', '$window', function ($document
, $window
) {
763 function getStyle(el
, cssprop
) {
764 if (el
.currentStyle
) { //IE
765 return el
.currentStyle
[cssprop
];
766 } else if ($window
.getComputedStyle
) {
767 return $window
.getComputedStyle(el
)[cssprop
];
769 // finally try and get inline style
770 return el
.style
[cssprop
];
774 * Checks if a given element is statically positioned
775 * @param element - raw DOM element
777 function isStaticPositioned(element
) {
778 return (getStyle(element
, "position") || 'static' ) === 'static';
782 * returns the closest, non-statically positioned parentOffset of a given element
785 var parentOffsetEl = function (element
) {
786 var docDomEl
= $document
[0];
787 var offsetParent
= element
.offsetParent
|| docDomEl
;
788 while (offsetParent
&& offsetParent
!== docDomEl
&& isStaticPositioned(offsetParent
) ) {
789 offsetParent
= offsetParent
.offsetParent
;
791 return offsetParent
|| docDomEl
;
796 * Provides read-only equivalent of jQuery's position function:
797 * http://api.jquery.com/position/
799 position: function (element
) {
800 var elBCR
= this.offset(element
);
801 var offsetParentBCR
= { top
: 0, left
: 0 };
802 var offsetParentEl
= parentOffsetEl(element
[0]);
803 if (offsetParentEl
!= $document
[0]) {
804 offsetParentBCR
= this.offset(angular
.element(offsetParentEl
));
805 offsetParentBCR
.top
+= offsetParentEl
.clientTop
- offsetParentEl
.scrollTop
;
806 offsetParentBCR
.left
+= offsetParentEl
.clientLeft
- offsetParentEl
.scrollLeft
;
809 var boundingClientRect
= element
[0].getBoundingClientRect();
811 width
: boundingClientRect
.width
|| element
.prop('offsetWidth'),
812 height
: boundingClientRect
.height
|| element
.prop('offsetHeight'),
813 top
: elBCR
.top
- offsetParentBCR
.top
,
814 left
: elBCR
.left
- offsetParentBCR
.left
819 * Provides read-only equivalent of jQuery's offset function:
820 * http://api.jquery.com/offset/
822 offset: function (element
) {
823 var boundingClientRect
= element
[0].getBoundingClientRect();
825 width
: boundingClientRect
.width
|| element
.prop('offsetWidth'),
826 height
: boundingClientRect
.height
|| element
.prop('offsetHeight'),
827 top
: boundingClientRect
.top
+ ($window
.pageYOffset
|| $document
[0].body
.scrollTop
|| $document
[0].documentElement
.scrollTop
),
828 left
: boundingClientRect
.left
+ ($window
.pageXOffset
|| $document
[0].body
.scrollLeft
|| $document
[0].documentElement
.scrollLeft
)
834 angular
.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
836 .constant('datepickerConfig', {
840 dayHeaderFormat
: 'EEE',
841 dayTitleFormat
: 'MMMM yyyy',
842 monthTitleFormat
: 'yyyy',
850 .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope
, $attrs
, dateFilter
, dtConfig
) {
852 day
: getValue($attrs
.dayFormat
, dtConfig
.dayFormat
),
853 month
: getValue($attrs
.monthFormat
, dtConfig
.monthFormat
),
854 year
: getValue($attrs
.yearFormat
, dtConfig
.yearFormat
),
855 dayHeader
: getValue($attrs
.dayHeaderFormat
, dtConfig
.dayHeaderFormat
),
856 dayTitle
: getValue($attrs
.dayTitleFormat
, dtConfig
.dayTitleFormat
),
857 monthTitle
: getValue($attrs
.monthTitleFormat
, dtConfig
.monthTitleFormat
)
859 startingDay
= getValue($attrs
.startingDay
, dtConfig
.startingDay
),
860 yearRange
= getValue($attrs
.yearRange
, dtConfig
.yearRange
);
862 this.minDate
= dtConfig
.minDate
? new Date(dtConfig
.minDate
) : null;
863 this.maxDate
= dtConfig
.maxDate
? new Date(dtConfig
.maxDate
) : null;
865 function getValue(value
, defaultValue
) {
866 return angular
.isDefined(value
) ? $scope
.$parent
.$eval(value
) : defaultValue
;
869 function getDaysInMonth( year
, month
) {
870 return new Date(year
, month
, 0).getDate();
873 function getDates(startDate
, n
) {
874 var dates
= new Array(n
);
875 var current
= startDate
, i
= 0;
877 dates
[i
++] = new Date(current
);
878 current
.setDate( current
.getDate() + 1 );
883 function makeDate(date
, format
, isSelected
, isSecondary
) {
884 return { date
: date
, label
: dateFilter(date
, format
), selected
: !!isSelected
, secondary
: !!isSecondary
};
890 getVisibleDates: function(date
, selected
) {
891 var year
= date
.getFullYear(), month
= date
.getMonth(), firstDayOfMonth
= new Date(year
, month
, 1);
892 var difference
= startingDay
- firstDayOfMonth
.getDay(),
893 numDisplayedFromPreviousMonth
= (difference
> 0) ? 7 - difference
: - difference
,
894 firstDate
= new Date(firstDayOfMonth
), numDates
= 0;
896 if ( numDisplayedFromPreviousMonth
> 0 ) {
897 firstDate
.setDate( - numDisplayedFromPreviousMonth
+ 1 );
898 numDates
+= numDisplayedFromPreviousMonth
; // Previous
900 numDates
+= getDaysInMonth(year
, month
+ 1); // Current
901 numDates
+= (7 - numDates
% 7) % 7; // Next
903 var days
= getDates(firstDate
, numDates
), labels
= new Array(7);
904 for (var i
= 0; i
< numDates
; i
++) {
905 var dt
= new Date(days
[i
]);
906 days
[i
] = makeDate(dt
, format
.day
, (selected
&& selected
.getDate() === dt
.getDate() && selected
.getMonth() === dt
.getMonth() && selected
.getFullYear() === dt
.getFullYear()), dt
.getMonth() !== month
);
908 for (var j
= 0; j
< 7; j
++) {
909 labels
[j
] = dateFilter(days
[j
].date
, format
.dayHeader
);
911 return { objects
: days
, title
: dateFilter(date
, format
.dayTitle
), labels
: labels
};
913 compare: function(date1
, date2
) {
914 return (new Date( date1
.getFullYear(), date1
.getMonth(), date1
.getDate() ) - new Date( date2
.getFullYear(), date2
.getMonth(), date2
.getDate() ) );
921 getVisibleDates: function(date
, selected
) {
922 var months
= new Array(12), year
= date
.getFullYear();
923 for ( var i
= 0; i
< 12; i
++ ) {
924 var dt
= new Date(year
, i
, 1);
925 months
[i
] = makeDate(dt
, format
.month
, (selected
&& selected
.getMonth() === i
&& selected
.getFullYear() === year
));
927 return { objects
: months
, title
: dateFilter(date
, format
.monthTitle
) };
929 compare: function(date1
, date2
) {
930 return new Date( date1
.getFullYear(), date1
.getMonth() ) - new Date( date2
.getFullYear(), date2
.getMonth() );
937 getVisibleDates: function(date
, selected
) {
938 var years
= new Array(yearRange
), year
= date
.getFullYear(), startYear
= parseInt((year
- 1) / yearRange
, 10) * yearRange
+ 1;
939 for ( var i
= 0; i
< yearRange
; i
++ ) {
940 var dt
= new Date(startYear
+ i
, 0, 1);
941 years
[i
] = makeDate(dt
, format
.year
, (selected
&& selected
.getFullYear() === dt
.getFullYear()));
943 return { objects
: years
, title
: [years
[0].label
, years
[yearRange
- 1].label
].join(' - ') };
945 compare: function(date1
, date2
) {
946 return date1
.getFullYear() - date2
.getFullYear();
949 step
: { years
: yearRange
}
953 this.isDisabled = function(date
, mode
) {
954 var currentMode
= this.modes
[mode
|| 0];
955 return ((this.minDate
&& currentMode
.compare(date
, this.minDate
) < 0) || (this.maxDate
&& currentMode
.compare(date
, this.maxDate
) > 0) || ($scope
.dateDisabled
&& $scope
.dateDisabled({date
: date
, mode
: currentMode
.name
})));
959 .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter
, $parse
, datepickerConfig
, $log
) {
963 templateUrl
: 'template/datepicker/datepicker.html',
967 require
: ['datepicker', '?^ngModel'],
968 controller
: 'DatepickerController',
969 link: function(scope
, element
, attrs
, ctrls
) {
970 var datepickerCtrl
= ctrls
[0], ngModel
= ctrls
[1];
973 return; // do nothing if no ng-model
976 // Configuration parameters
977 var mode
= 0, selected
= new Date(), showWeeks
= datepickerConfig
.showWeeks
;
979 if (attrs
.showWeeks
) {
980 scope
.$parent
.$watch($parse(attrs
.showWeeks
), function(value
) {
981 showWeeks
= !! value
;
982 updateShowWeekNumbers();
985 updateShowWeekNumbers();
989 scope
.$parent
.$watch($parse(attrs
.min
), function(value
) {
990 datepickerCtrl
.minDate
= value
? new Date(value
) : null;
995 scope
.$parent
.$watch($parse(attrs
.max
), function(value
) {
996 datepickerCtrl
.maxDate
= value
? new Date(value
) : null;
1001 function updateShowWeekNumbers() {
1002 scope
.showWeekNumbers
= mode
=== 0 && showWeeks
;
1005 // Split array into smaller arrays
1006 function split(arr
, size
) {
1008 while (arr
.length
> 0) {
1009 arrays
.push(arr
.splice(0, size
));
1014 function refill( updateSelected
) {
1015 var date
= null, valid
= true;
1017 if ( ngModel
.$modelValue
) {
1018 date
= new Date( ngModel
.$modelValue
);
1020 if ( isNaN(date
) ) {
1022 $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.');
1023 } else if ( updateSelected
) {
1027 ngModel
.$setValidity('date', valid
);
1029 var currentMode
= datepickerCtrl
.modes
[mode
], data
= currentMode
.getVisibleDates(selected
, date
);
1030 angular
.forEach(data
.objects
, function(obj
) {
1031 obj
.disabled
= datepickerCtrl
.isDisabled(obj
.date
, mode
);
1034 ngModel
.$setValidity('date-disabled', (!date
|| !datepickerCtrl
.isDisabled(date
)));
1036 scope
.rows
= split(data
.objects
, currentMode
.split
);
1037 scope
.labels
= data
.labels
|| [];
1038 scope
.title
= data
.title
;
1041 function setMode(value
) {
1043 updateShowWeekNumbers();
1047 ngModel
.$render = function() {
1051 scope
.select = function( date
) {
1053 var dt
= ngModel
.$modelValue
? new Date( ngModel
.$modelValue
) : new Date(0, 0, 0, 0, 0, 0, 0);
1054 dt
.setFullYear( date
.getFullYear(), date
.getMonth(), date
.getDate() );
1055 ngModel
.$setViewValue( dt
);
1059 setMode( mode
- 1 );
1062 scope
.move = function(direction
) {
1063 var step
= datepickerCtrl
.modes
[mode
].step
;
1064 selected
.setMonth( selected
.getMonth() + direction
* (step
.months
|| 0) );
1065 selected
.setFullYear( selected
.getFullYear() + direction
* (step
.years
|| 0) );
1068 scope
.toggleMode = function() {
1069 setMode( (mode
+ 1) % datepickerCtrl
.modes
.length
);
1071 scope
.getWeekNumber = function(row
) {
1072 return ( mode
=== 0 && scope
.showWeekNumbers
&& row
.length
=== 7 ) ? getISO8601WeekNumber(row
[0].date
) : null;
1075 function getISO8601WeekNumber(date
) {
1076 var checkDate
= new Date(date
);
1077 checkDate
.setDate(checkDate
.getDate() + 4 - (checkDate
.getDay() || 7)); // Thursday
1078 var time
= checkDate
.getTime();
1079 checkDate
.setMonth(0); // Compare with Jan 1
1080 checkDate
.setDate(1);
1081 return Math
.floor(Math
.round((time
- checkDate
) / 86400000) / 7) + 1;
1087 .constant('datepickerPopupConfig', {
1088 dateFormat
: 'yyyy-MM-dd',
1089 currentText
: 'Today',
1090 toggleWeeksText
: 'Weeks',
1093 closeOnDateSelection
: true,
1094 appendToBody
: false,
1098 .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
1099 function ($compile
, $parse
, $document
, $position
, dateFilter
, datepickerPopupConfig
, datepickerConfig
) {
1103 link: function(originalScope
, element
, attrs
, ngModel
) {
1104 var scope
= originalScope
.$new(), // create a child scope so we are not polluting original one
1106 closeOnDateSelection
= angular
.isDefined(attrs
.closeOnDateSelection
) ? originalScope
.$eval(attrs
.closeOnDateSelection
) : datepickerPopupConfig
.closeOnDateSelection
,
1107 appendToBody
= angular
.isDefined(attrs
.datepickerAppendToBody
) ? originalScope
.$eval(attrs
.datepickerAppendToBody
) : datepickerPopupConfig
.appendToBody
;
1109 attrs
.$observe('datepickerPopup', function(value
) {
1110 dateFormat
= value
|| datepickerPopupConfig
.dateFormat
;
1114 scope
.showButtonBar
= angular
.isDefined(attrs
.showButtonBar
) ? originalScope
.$eval(attrs
.showButtonBar
) : datepickerPopupConfig
.showButtonBar
;
1116 originalScope
.$on('$destroy', function() {
1121 attrs
.$observe('currentText', function(text
) {
1122 scope
.currentText
= angular
.isDefined(text
) ? text
: datepickerPopupConfig
.currentText
;
1124 attrs
.$observe('toggleWeeksText', function(text
) {
1125 scope
.toggleWeeksText
= angular
.isDefined(text
) ? text
: datepickerPopupConfig
.toggleWeeksText
;
1127 attrs
.$observe('clearText', function(text
) {
1128 scope
.clearText
= angular
.isDefined(text
) ? text
: datepickerPopupConfig
.clearText
;
1130 attrs
.$observe('closeText', function(text
) {
1131 scope
.closeText
= angular
.isDefined(text
) ? text
: datepickerPopupConfig
.closeText
;
1134 var getIsOpen
, setIsOpen
;
1135 if ( attrs
.isOpen
) {
1136 getIsOpen
= $parse(attrs
.isOpen
);
1137 setIsOpen
= getIsOpen
.assign
;
1139 originalScope
.$watch(getIsOpen
, function updateOpen(value
) {
1140 scope
.isOpen
= !! value
;
1143 scope
.isOpen
= getIsOpen
? getIsOpen(originalScope
) : false; // Initial state
1145 function setOpen( value
) {
1147 setIsOpen(originalScope
, !!value
);
1149 scope
.isOpen
= !!value
;
1153 var documentClickBind = function(event
) {
1154 if (scope
.isOpen
&& event
.target
!== element
[0]) {
1155 scope
.$apply(function() {
1161 var elementFocusBind = function() {
1162 scope
.$apply(function() {
1167 // popup element used to display calendar
1168 var popupEl
= angular
.element('<div datepicker-popup-wrap><div datepicker></div></div>');
1171 'ng-change': 'dateSelection()'
1173 var datepickerEl
= angular
.element(popupEl
.children()[0]);
1174 if (attrs
.datepickerOptions
) {
1175 datepickerEl
.attr(angular
.extend({}, originalScope
.$eval(attrs
.datepickerOptions
)));
1178 // TODO: reverse from dateFilter string to Date object
1179 function parseDate(viewValue
) {
1181 ngModel
.$setValidity('date', true);
1183 } else if (angular
.isDate(viewValue
)) {
1184 ngModel
.$setValidity('date', true);
1186 } else if (angular
.isString(viewValue
)) {
1187 var date
= new Date(viewValue
);
1189 ngModel
.$setValidity('date', false);
1192 ngModel
.$setValidity('date', true);
1196 ngModel
.$setValidity('date', false);
1200 ngModel
.$parsers
.unshift(parseDate
);
1203 scope
.dateSelection = function(dt
) {
1204 if (angular
.isDefined(dt
)) {
1207 ngModel
.$setViewValue(scope
.date
);
1210 if (closeOnDateSelection
) {
1215 element
.bind('input change keyup', function() {
1216 scope
.$apply(function() {
1217 scope
.date
= ngModel
.$modelValue
;
1222 ngModel
.$render = function() {
1223 var date
= ngModel
.$viewValue
? dateFilter(ngModel
.$viewValue
, dateFormat
) : '';
1225 scope
.date
= ngModel
.$modelValue
;
1228 function addWatchableAttribute(attribute
, scopeProperty
, datepickerAttribute
) {
1230 originalScope
.$watch($parse(attribute
), function(value
){
1231 scope
[scopeProperty
] = value
;
1233 datepickerEl
.attr(datepickerAttribute
|| scopeProperty
, scopeProperty
);
1236 addWatchableAttribute(attrs
.min
, 'min');
1237 addWatchableAttribute(attrs
.max
, 'max');
1238 if (attrs
.showWeeks
) {
1239 addWatchableAttribute(attrs
.showWeeks
, 'showWeeks', 'show-weeks');
1241 scope
.showWeeks
= datepickerConfig
.showWeeks
;
1242 datepickerEl
.attr('show-weeks', 'showWeeks');
1244 if (attrs
.dateDisabled
) {
1245 datepickerEl
.attr('date-disabled', attrs
.dateDisabled
);
1248 function updatePosition() {
1249 scope
.position
= appendToBody
? $position
.offset(element
) : $position
.position(element
);
1250 scope
.position
.top
= scope
.position
.top
+ element
.prop('offsetHeight');
1253 var documentBindingInitialized
= false, elementFocusInitialized
= false;
1254 scope
.$watch('isOpen', function(value
) {
1257 $document
.bind('click', documentClickBind
);
1258 if(elementFocusInitialized
) {
1259 element
.unbind('focus', elementFocusBind
);
1262 documentBindingInitialized
= true;
1264 if(documentBindingInitialized
) {
1265 $document
.unbind('click', documentClickBind
);
1267 element
.bind('focus', elementFocusBind
);
1268 elementFocusInitialized
= true;
1272 setIsOpen(originalScope
, value
);
1276 scope
.today = function() {
1277 scope
.dateSelection(new Date());
1279 scope
.clear = function() {
1280 scope
.dateSelection(null);
1283 var $popup
= $compile(popupEl
)(scope
);
1284 if ( appendToBody
) {
1285 $document
.find('body').append($popup
);
1287 element
.after($popup
);
1293 .directive('datepickerPopupWrap', function() {
1298 templateUrl
: 'template/datepicker/popup.html',
1299 link:function (scope
, element
, attrs
) {
1300 element
.bind('click', function(event
) {
1301 event
.preventDefault();
1302 event
.stopPropagation();
1309 * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
1310 * @restrict class or attribute
1312 <li class="dropdown">
1313 <a class="dropdown-toggle">My Dropdown Menu</a>
1314 <ul class="dropdown-menu">
1315 <li ng-repeat="choice in dropChoices">
1316 <a ng-href="{{choice.href}}">{{choice.text}}</a>
1322 angular
.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document
, $location
) {
1323 var openElement
= null,
1324 closeMenu
= angular
.noop
;
1327 link: function(scope
, element
, attrs
) {
1328 scope
.$watch('$location.path', function() { closeMenu(); });
1329 element
.parent().bind('click', function() { closeMenu(); });
1330 element
.bind('click', function (event
) {
1332 var elementWasOpen
= (element
=== openElement
);
1334 event
.preventDefault();
1335 event
.stopPropagation();
1337 if (!!openElement
) {
1341 if (!elementWasOpen
&& !element
.hasClass('disabled') && !element
.prop('disabled')) {
1342 element
.parent().addClass('open');
1343 openElement
= element
;
1344 closeMenu = function (event
) {
1346 event
.preventDefault();
1347 event
.stopPropagation();
1349 $document
.unbind('click', closeMenu
);
1350 element
.parent().removeClass('open');
1351 closeMenu
= angular
.noop
;
1354 $document
.bind('click', closeMenu
);
1361 angular
.module('ui.bootstrap.modal', [])
1364 * A helper, internal data structure that acts as a map but also allows getting / removing
1365 * elements in the LIFO order
1367 .factory('$$stackedMap', function () {
1369 createNew: function () {
1373 add: function (key
, value
) {
1379 get: function (key
) {
1380 for (var i
= 0; i
< stack
.length
; i
++) {
1381 if (key
== stack
[i
].key
) {
1388 for (var i
= 0; i
< stack
.length
; i
++) {
1389 keys
.push(stack
[i
].key
);
1394 return stack
[stack
.length
- 1];
1396 remove: function (key
) {
1398 for (var i
= 0; i
< stack
.length
; i
++) {
1399 if (key
== stack
[i
].key
) {
1404 return stack
.splice(idx
, 1)[0];
1406 removeTop: function () {
1407 return stack
.splice(stack
.length
- 1, 1)[0];
1409 length: function () {
1410 return stack
.length
;
1418 * A helper directive for the $modal service. It creates a backdrop element.
1420 .directive('modalBackdrop', ['$timeout', function ($timeout
) {
1424 templateUrl
: 'template/modal/backdrop.html',
1425 link: function (scope
) {
1427 scope
.animate
= false;
1429 //trigger CSS transitions
1430 $timeout(function () {
1431 scope
.animate
= true;
1437 .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack
, $timeout
) {
1445 templateUrl
: 'template/modal/window.html',
1446 link: function (scope
, element
, attrs
) {
1447 scope
.windowClass
= attrs
.windowClass
|| '';
1449 $timeout(function () {
1450 // trigger CSS transitions
1451 scope
.animate
= true;
1452 // focus a freshly-opened modal
1456 scope
.close = function (evt
) {
1457 var modal
= $modalStack
.getTop();
1458 if (modal
&& modal
.value
.backdrop
&& modal
.value
.backdrop
!= 'static' && (evt
.target
=== evt
.currentTarget
)) {
1459 evt
.preventDefault();
1460 evt
.stopPropagation();
1461 $modalStack
.dismiss(modal
.key
, 'backdrop click');
1468 .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
1469 function ($document
, $compile
, $rootScope
, $$stackedMap
) {
1471 var OPENED_MODAL_CLASS
= 'modal-open';
1473 var backdropjqLiteEl
, backdropDomEl
;
1474 var backdropScope
= $rootScope
.$new(true);
1475 var openedWindows
= $$stackedMap
.createNew();
1476 var $modalStack
= {};
1478 function backdropIndex() {
1479 var topBackdropIndex
= -1;
1480 var opened
= openedWindows
.keys();
1481 for (var i
= 0; i
< opened
.length
; i
++) {
1482 if (openedWindows
.get(opened
[i
]).value
.backdrop
) {
1483 topBackdropIndex
= i
;
1486 return topBackdropIndex
;
1489 $rootScope
.$watch(backdropIndex
, function(newBackdropIndex
){
1490 backdropScope
.index
= newBackdropIndex
;
1493 function removeModalWindow(modalInstance
) {
1495 var body
= $document
.find('body').eq(0);
1496 var modalWindow
= openedWindows
.get(modalInstance
).value
;
1498 //clean up the stack
1499 openedWindows
.remove(modalInstance
);
1501 //remove window DOM element
1502 modalWindow
.modalDomEl
.remove();
1503 body
.toggleClass(OPENED_MODAL_CLASS
, openedWindows
.length() > 0);
1505 //remove backdrop if no longer needed
1506 if (backdropDomEl
&& backdropIndex() == -1) {
1507 backdropDomEl
.remove();
1508 backdropDomEl
= undefined;
1512 modalWindow
.modalScope
.$destroy();
1515 $document
.bind('keydown', function (evt
) {
1518 if (evt
.which
=== 27) {
1519 modal
= openedWindows
.top();
1520 if (modal
&& modal
.value
.keyboard
) {
1521 $rootScope
.$apply(function () {
1522 $modalStack
.dismiss(modal
.key
);
1528 $modalStack
.open = function (modalInstance
, modal
) {
1530 openedWindows
.add(modalInstance
, {
1531 deferred
: modal
.deferred
,
1532 modalScope
: modal
.scope
,
1533 backdrop
: modal
.backdrop
,
1534 keyboard
: modal
.keyboard
1537 var body
= $document
.find('body').eq(0);
1539 if (backdropIndex() >= 0 && !backdropDomEl
) {
1540 backdropjqLiteEl
= angular
.element('<div modal-backdrop></div>');
1541 backdropDomEl
= $compile(backdropjqLiteEl
)(backdropScope
);
1542 body
.append(backdropDomEl
);
1545 var angularDomEl
= angular
.element('<div modal-window></div>');
1546 angularDomEl
.attr('window-class', modal
.windowClass
);
1547 angularDomEl
.attr('index', openedWindows
.length() - 1);
1548 angularDomEl
.html(modal
.content
);
1550 var modalDomEl
= $compile(angularDomEl
)(modal
.scope
);
1551 openedWindows
.top().value
.modalDomEl
= modalDomEl
;
1552 body
.append(modalDomEl
);
1553 body
.addClass(OPENED_MODAL_CLASS
);
1556 $modalStack
.close = function (modalInstance
, result
) {
1557 var modalWindow
= openedWindows
.get(modalInstance
).value
;
1559 modalWindow
.deferred
.resolve(result
);
1560 removeModalWindow(modalInstance
);
1564 $modalStack
.dismiss = function (modalInstance
, reason
) {
1565 var modalWindow
= openedWindows
.get(modalInstance
).value
;
1567 modalWindow
.deferred
.reject(reason
);
1568 removeModalWindow(modalInstance
);
1572 $modalStack
.getTop = function () {
1573 return openedWindows
.top();
1579 .provider('$modal', function () {
1581 var $modalProvider
= {
1583 backdrop
: true, //can be also false or 'static'
1586 $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
1587 function ($injector
, $rootScope
, $q
, $http
, $templateCache
, $controller
, $modalStack
) {
1591 function getTemplatePromise(options
) {
1592 return options
.template
? $q
.when(options
.template
) :
1593 $http
.get(options
.templateUrl
, {cache
: $templateCache
}).then(function (result
) {
1598 function getResolvePromises(resolves
) {
1599 var promisesArr
= [];
1600 angular
.forEach(resolves
, function (value
, key
) {
1601 if (angular
.isFunction(value
) || angular
.isArray(value
)) {
1602 promisesArr
.push($q
.when($injector
.invoke(value
)));
1608 $modal
.open = function (modalOptions
) {
1610 var modalResultDeferred
= $q
.defer();
1611 var modalOpenedDeferred
= $q
.defer();
1613 //prepare an instance of a modal to be injected into controllers and returned to a caller
1614 var modalInstance
= {
1615 result
: modalResultDeferred
.promise
,
1616 opened
: modalOpenedDeferred
.promise
,
1617 close: function (result
) {
1618 $modalStack
.close(modalInstance
, result
);
1620 dismiss: function (reason
) {
1621 $modalStack
.dismiss(modalInstance
, reason
);
1625 //merge and clean up options
1626 modalOptions
= angular
.extend({}, $modalProvider
.options
, modalOptions
);
1627 modalOptions
.resolve
= modalOptions
.resolve
|| {};
1630 if (!modalOptions
.template
&& !modalOptions
.templateUrl
) {
1631 throw new Error('One of template or templateUrl options is required.');
1634 var templateAndResolvePromise
=
1635 $q
.all([getTemplatePromise(modalOptions
)].concat(getResolvePromises(modalOptions
.resolve
)));
1638 templateAndResolvePromise
.then(function resolveSuccess(tplAndVars
) {
1640 var modalScope
= (modalOptions
.scope
|| $rootScope
).$new();
1641 modalScope
.$close
= modalInstance
.close
;
1642 modalScope
.$dismiss
= modalInstance
.dismiss
;
1644 var ctrlInstance
, ctrlLocals
= {};
1645 var resolveIter
= 1;
1648 if (modalOptions
.controller
) {
1649 ctrlLocals
.$scope
= modalScope
;
1650 ctrlLocals
.$modalInstance
= modalInstance
;
1651 angular
.forEach(modalOptions
.resolve
, function (value
, key
) {
1652 ctrlLocals
[key
] = tplAndVars
[resolveIter
++];
1655 ctrlInstance
= $controller(modalOptions
.controller
, ctrlLocals
);
1658 $modalStack
.open(modalInstance
, {
1660 deferred
: modalResultDeferred
,
1661 content
: tplAndVars
[0],
1662 backdrop
: modalOptions
.backdrop
,
1663 keyboard
: modalOptions
.keyboard
,
1664 windowClass
: modalOptions
.windowClass
1667 }, function resolveError(reason
) {
1668 modalResultDeferred
.reject(reason
);
1671 templateAndResolvePromise
.then(function () {
1672 modalOpenedDeferred
.resolve(true);
1674 modalOpenedDeferred
.reject(false);
1677 return modalInstance
;
1684 return $modalProvider
;
1687 angular
.module('ui.bootstrap.pagination', [])
1689 .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope
, $attrs
, $parse
, $interpolate
) {
1691 setNumPages
= $attrs
.numPages
? $parse($attrs
.numPages
).assign
: angular
.noop
;
1693 this.init = function(defaultItemsPerPage
) {
1694 if ($attrs
.itemsPerPage
) {
1695 $scope
.$parent
.$watch($parse($attrs
.itemsPerPage
), function(value
) {
1696 self
.itemsPerPage
= parseInt(value
, 10);
1697 $scope
.totalPages
= self
.calculateTotalPages();
1700 this.itemsPerPage
= defaultItemsPerPage
;
1704 this.noPrevious = function() {
1705 return this.page
=== 1;
1707 this.noNext = function() {
1708 return this.page
=== $scope
.totalPages
;
1711 this.isActive = function(page
) {
1712 return this.page
=== page
;
1715 this.calculateTotalPages = function() {
1716 var totalPages
= this.itemsPerPage
< 1 ? 1 : Math
.ceil($scope
.totalItems
/ this.itemsPerPage
);
1717 return Math
.max(totalPages
|| 0, 1);
1720 this.getAttributeValue = function(attribute
, defaultValue
, interpolate
) {
1721 return angular
.isDefined(attribute
) ? (interpolate
? $interpolate(attribute
)($scope
.$parent
) : $scope
.$parent
.$eval(attribute
)) : defaultValue
;
1724 this.render = function() {
1725 this.page
= parseInt($scope
.page
, 10) || 1;
1726 if (this.page
> 0 && this.page
<= $scope
.totalPages
) {
1727 $scope
.pages
= this.getPages(this.page
, $scope
.totalPages
);
1731 $scope
.selectPage = function(page
) {
1732 if ( ! self
.isActive(page
) && page
> 0 && page
<= $scope
.totalPages
) {
1734 $scope
.onSelectPage({ page
: page
});
1738 $scope
.$watch('page', function() {
1742 $scope
.$watch('totalItems', function() {
1743 $scope
.totalPages
= self
.calculateTotalPages();
1746 $scope
.$watch('totalPages', function(value
) {
1747 setNumPages($scope
.$parent
, value
); // Readonly variable
1749 if ( self
.page
> value
) {
1750 $scope
.selectPage(value
);
1757 .constant('paginationConfig', {
1759 boundaryLinks
: false,
1760 directionLinks
: true,
1762 previousText
: 'Previous',
1768 .directive('pagination', ['$parse', 'paginationConfig', function($parse
, config
) {
1776 controller
: 'PaginationController',
1777 templateUrl
: 'template/pagination/pagination.html',
1779 link: function(scope
, element
, attrs
, paginationCtrl
) {
1781 // Setup configuration parameters
1783 boundaryLinks
= paginationCtrl
.getAttributeValue(attrs
.boundaryLinks
, config
.boundaryLinks
),
1784 directionLinks
= paginationCtrl
.getAttributeValue(attrs
.directionLinks
, config
.directionLinks
),
1785 firstText
= paginationCtrl
.getAttributeValue(attrs
.firstText
, config
.firstText
, true),
1786 previousText
= paginationCtrl
.getAttributeValue(attrs
.previousText
, config
.previousText
, true),
1787 nextText
= paginationCtrl
.getAttributeValue(attrs
.nextText
, config
.nextText
, true),
1788 lastText
= paginationCtrl
.getAttributeValue(attrs
.lastText
, config
.lastText
, true),
1789 rotate
= paginationCtrl
.getAttributeValue(attrs
.rotate
, config
.rotate
);
1791 paginationCtrl
.init(config
.itemsPerPage
);
1793 if (attrs
.maxSize
) {
1794 scope
.$parent
.$watch($parse(attrs
.maxSize
), function(value
) {
1795 maxSize
= parseInt(value
, 10);
1796 paginationCtrl
.render();
1800 // Create page object used in template
1801 function makePage(number
, text
, isActive
, isDisabled
) {
1806 disabled
: isDisabled
1810 paginationCtrl
.getPages = function(currentPage
, totalPages
) {
1813 // Default page limits
1814 var startPage
= 1, endPage
= totalPages
;
1815 var isMaxSized
= ( angular
.isDefined(maxSize
) && maxSize
< totalPages
);
1817 // recompute if maxSize
1820 // Current page is displayed in the middle of the visible ones
1821 startPage
= Math
.max(currentPage
- Math
.floor(maxSize
/2), 1);
1822 endPage
= startPage
+ maxSize
- 1;
1824 // Adjust if limit is exceeded
1825 if (endPage
> totalPages
) {
1826 endPage
= totalPages
;
1827 startPage
= endPage
- maxSize
+ 1;
1830 // Visible pages are paginated with maxSize
1831 startPage
= ((Math
.ceil(currentPage
/ maxSize
) - 1) * maxSize
) + 1;
1833 // Adjust last page if limit is exceeded
1834 endPage
= Math
.min(startPage
+ maxSize
- 1, totalPages
);
1838 // Add page number links
1839 for (var number
= startPage
; number
<= endPage
; number
++) {
1840 var page
= makePage(number
, number
, paginationCtrl
.isActive(number
), false);
1844 // Add links to move between page sets
1845 if ( isMaxSized
&& ! rotate
) {
1846 if ( startPage
> 1 ) {
1847 var previousPageSet
= makePage(startPage
- 1, '...', false, false);
1848 pages
.unshift(previousPageSet
);
1851 if ( endPage
< totalPages
) {
1852 var nextPageSet
= makePage(endPage
+ 1, '...', false, false);
1853 pages
.push(nextPageSet
);
1857 // Add previous & next links
1858 if (directionLinks
) {
1859 var previousPage
= makePage(currentPage
- 1, previousText
, false, paginationCtrl
.noPrevious());
1860 pages
.unshift(previousPage
);
1862 var nextPage
= makePage(currentPage
+ 1, nextText
, false, paginationCtrl
.noNext());
1863 pages
.push(nextPage
);
1866 // Add first & last links
1867 if (boundaryLinks
) {
1868 var firstPage
= makePage(1, firstText
, false, paginationCtrl
.noPrevious());
1869 pages
.unshift(firstPage
);
1871 var lastPage
= makePage(totalPages
, lastText
, false, paginationCtrl
.noNext());
1872 pages
.push(lastPage
);
1881 .constant('pagerConfig', {
1883 previousText
: '« Previous',
1884 nextText
: 'Next »',
1888 .directive('pager', ['pagerConfig', function(config
) {
1896 controller
: 'PaginationController',
1897 templateUrl
: 'template/pagination/pager.html',
1899 link: function(scope
, element
, attrs
, paginationCtrl
) {
1901 // Setup configuration parameters
1902 var previousText
= paginationCtrl
.getAttributeValue(attrs
.previousText
, config
.previousText
, true),
1903 nextText
= paginationCtrl
.getAttributeValue(attrs
.nextText
, config
.nextText
, true),
1904 align
= paginationCtrl
.getAttributeValue(attrs
.align
, config
.align
);
1906 paginationCtrl
.init(config
.itemsPerPage
);
1908 // Create page object used in template
1909 function makePage(number
, text
, isDisabled
, isPrevious
, isNext
) {
1913 disabled
: isDisabled
,
1914 previous
: ( align
&& isPrevious
),
1915 next
: ( align
&& isNext
)
1919 paginationCtrl
.getPages = function(currentPage
) {
1921 makePage(currentPage
- 1, previousText
, paginationCtrl
.noPrevious(), true, false),
1922 makePage(currentPage
+ 1, nextText
, paginationCtrl
.noNext(), false, true)
1930 * The following features are still outstanding: animation as a
1931 * function, placement as a function, inside, support for more triggers than
1932 * just mouse enter/leave, html tooltips, and selector delegation.
1934 angular
.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
1937 * The $tooltip service creates tooltip- and popover-like directives as well as
1938 * houses global options for them.
1940 .provider( '$tooltip', function () {
1941 // The default options tooltip and popover.
1942 var defaultOptions
= {
1948 // Default hide triggers for each show trigger
1950 'mouseenter': 'mouseleave',
1955 // The options specified to the provider globally.
1956 var globalOptions
= {};
1959 * `options({})` allows global configuration of all tooltips in the
1962 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
1963 * // place tooltips left instead of top by default
1964 * $tooltipProvider.options( { placement: 'left' } );
1967 this.options = function( value
) {
1968 angular
.extend( globalOptions
, value
);
1972 * This allows you to extend the set of trigger mappings available. E.g.:
1974 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
1976 this.setTriggers
= function setTriggers ( triggers
) {
1977 angular
.extend( triggerMap
, triggers
);
1981 * This is a helper function for translating camel-case to snake-case.
1983 function snake_case(name
){
1984 var regexp
= /[A-Z]/g;
1985 var separator
= '-';
1986 return name
.replace(regexp
, function(letter
, pos
) {
1987 return (pos
? separator
: '') + letter
.toLowerCase();
1992 * Returns the actual instance of the $tooltip service.
1993 * TODO support multiple triggers
1995 this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window
, $compile
, $timeout
, $parse
, $document
, $position
, $interpolate
) {
1996 return function $tooltip ( type
, prefix
, defaultTriggerShow
) {
1997 var options
= angular
.extend( {}, defaultOptions
, globalOptions
);
2000 * Returns an object of show and hide triggers.
2002 * If a trigger is supplied,
2003 * it is used to show the tooltip; otherwise, it will use the `trigger`
2004 * option passed to the `$tooltipProvider.options` method; else it will
2005 * default to the trigger supplied to this directive factory.
2007 * The hide trigger is based on the show trigger. If the `trigger` option
2008 * was passed to the `$tooltipProvider.options` method, it will use the
2009 * mapped trigger from `triggerMap` or the passed trigger if the map is
2010 * undefined; otherwise, it uses the `triggerMap` value of the show
2011 * trigger; else it will just use the show trigger.
2013 function getTriggers ( trigger
) {
2014 var show
= trigger
|| options
.trigger
|| defaultTriggerShow
;
2015 var hide
= triggerMap
[show
] || show
;
2022 var directiveName
= snake_case( type
);
2024 var startSym
= $interpolate
.startSymbol();
2025 var endSym
= $interpolate
.endSymbol();
2027 '<div '+ directiveName
+'-popup '+
2028 'title="'+startSym
+'tt_title'+endSym
+'" '+
2029 'content="'+startSym
+'tt_content'+endSym
+'" '+
2030 'placement="'+startSym
+'tt_placement'+endSym
+'" '+
2031 'animation="tt_animation" '+
2032 'is-open="tt_isOpen"'+
2039 link
: function link ( scope
, element
, attrs
) {
2040 var tooltip
= $compile( template
)( scope
);
2041 var transitionTimeout
;
2043 var appendToBody
= angular
.isDefined( options
.appendToBody
) ? options
.appendToBody
: false;
2044 var triggers
= getTriggers( undefined );
2045 var hasRegisteredTriggers
= false;
2046 var hasEnableExp
= angular
.isDefined(attrs
[prefix
+'Enable']);
2048 var positionTooltip = function (){
2053 // Get the position of the directive element.
2054 position
= appendToBody
? $position
.offset( element
) : $position
.position( element
);
2056 // Get the height and width of the tooltip so we can center it.
2057 ttWidth
= tooltip
.prop( 'offsetWidth' );
2058 ttHeight
= tooltip
.prop( 'offsetHeight' );
2060 // Calculate the tooltip's top and left coordinates to center it with
2062 switch ( scope
.tt_placement
) {
2065 top
: position
.top
+ position
.height
/ 2 - ttHeight
/ 2,
2066 left
: position
.left
+ position
.width
2071 top
: position
.top
+ position
.height
,
2072 left
: position
.left
+ position
.width
/ 2 - ttWidth
/ 2
2077 top
: position
.top
+ position
.height
/ 2 - ttHeight
/ 2,
2078 left
: position
.left
- ttWidth
2083 top
: position
.top
- ttHeight
,
2084 left
: position
.left
+ position
.width
/ 2 - ttWidth
/ 2
2089 ttPosition
.top
+= 'px';
2090 ttPosition
.left
+= 'px';
2092 // Now set the calculated positioning.
2093 tooltip
.css( ttPosition
);
2097 // By default, the tooltip is not open.
2098 // TODO add ability to start tooltip opened
2099 scope
.tt_isOpen
= false;
2101 function toggleTooltipBind () {
2102 if ( ! scope
.tt_isOpen
) {
2109 // Show the tooltip with delay if specified, otherwise show it immediately
2110 function showTooltipBind() {
2111 if(hasEnableExp
&& !scope
.$eval(attrs
[prefix
+'Enable'])) {
2114 if ( scope
.tt_popupDelay
) {
2115 popupTimeout
= $timeout( show
, scope
.tt_popupDelay
);
2116 popupTimeout
.then(function(reposition
){reposition();});
2118 scope
.$apply( show
)();
2122 function hideTooltipBind () {
2123 scope
.$apply(function () {
2128 // Show the tooltip popup element.
2132 // Don't show empty tooltips.
2133 if ( ! scope
.tt_content
) {
2134 return angular
.noop
;
2137 // If there is a pending remove transition, we must cancel it, lest the
2138 // tooltip be mysteriously removed.
2139 if ( transitionTimeout
) {
2140 $timeout
.cancel( transitionTimeout
);
2143 // Set the initial positioning.
2144 tooltip
.css({ top
: 0, left
: 0, display
: 'block' });
2146 // Now we add it to the DOM because need some info about it. But it's not
2147 // visible yet anyway.
2148 if ( appendToBody
) {
2149 $document
.find( 'body' ).append( tooltip
);
2151 element
.after( tooltip
);
2156 // And show the tooltip.
2157 scope
.tt_isOpen
= true;
2159 // Return positioning function as promise callback for correct
2160 // positioning after draw.
2161 return positionTooltip
;
2164 // Hide the tooltip popup element.
2166 // First things first: we don't show it anymore.
2167 scope
.tt_isOpen
= false;
2169 //if tooltip is going to be shown after delay, we must cancel this
2170 $timeout
.cancel( popupTimeout
);
2172 // And now we remove it from the DOM. However, if we have animation, we
2173 // need to wait for it to expire beforehand.
2174 // FIXME: this is a placeholder for a port of the transitions library.
2175 if ( scope
.tt_animation
) {
2176 transitionTimeout
= $timeout(function () {
2185 * Observe the relevant attributes.
2187 attrs
.$observe( type
, function ( val
) {
2188 scope
.tt_content
= val
;
2190 if (!val
&& scope
.tt_isOpen
) {
2195 attrs
.$observe( prefix
+'Title', function ( val
) {
2196 scope
.tt_title
= val
;
2199 attrs
.$observe( prefix
+'Placement', function ( val
) {
2200 scope
.tt_placement
= angular
.isDefined( val
) ? val
: options
.placement
;
2203 attrs
.$observe( prefix
+'PopupDelay', function ( val
) {
2204 var delay
= parseInt( val
, 10 );
2205 scope
.tt_popupDelay
= ! isNaN(delay
) ? delay
: options
.popupDelay
;
2208 var unregisterTriggers = function() {
2209 if (hasRegisteredTriggers
) {
2210 element
.unbind( triggers
.show
, showTooltipBind
);
2211 element
.unbind( triggers
.hide
, hideTooltipBind
);
2215 attrs
.$observe( prefix
+'Trigger', function ( val
) {
2216 unregisterTriggers();
2218 triggers
= getTriggers( val
);
2220 if ( triggers
.show
=== triggers
.hide
) {
2221 element
.bind( triggers
.show
, toggleTooltipBind
);
2223 element
.bind( triggers
.show
, showTooltipBind
);
2224 element
.bind( triggers
.hide
, hideTooltipBind
);
2227 hasRegisteredTriggers
= true;
2230 var animation
= scope
.$eval(attrs
[prefix
+ 'Animation']);
2231 scope
.tt_animation
= angular
.isDefined(animation
) ? !!animation
: options
.animation
;
2233 attrs
.$observe( prefix
+'AppendToBody', function ( val
) {
2234 appendToBody
= angular
.isDefined( val
) ? $parse( val
)( scope
) : appendToBody
;
2237 // if a tooltip is attached to <body> we need to remove it on
2238 // location change as its parent scope will probably not be destroyed
2240 if ( appendToBody
) {
2241 scope
.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
2242 if ( scope
.tt_isOpen
) {
2248 // Make sure tooltip is destroyed and removed.
2249 scope
.$on('$destroy', function onDestroyTooltip() {
2250 $timeout
.cancel( transitionTimeout
);
2251 $timeout
.cancel( popupTimeout
);
2252 unregisterTriggers();
2263 .directive( 'tooltipPopup', function () {
2267 scope
: { content
: '@', placement
: '@', animation
: '&', isOpen
: '&' },
2268 templateUrl
: 'template/tooltip/tooltip-popup.html'
2272 .directive( 'tooltip', [ '$tooltip', function ( $tooltip
) {
2273 return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
2276 .directive( 'tooltipHtmlUnsafePopup', function () {
2280 scope
: { content
: '@', placement
: '@', animation
: '&', isOpen
: '&' },
2281 templateUrl
: 'template/tooltip/tooltip-html-unsafe-popup.html'
2285 .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip
) {
2286 return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
2290 * The following features are still outstanding: popup delay, animation as a
2291 * function, placement as a function, inside, support for more triggers than
2292 * just mouse enter/leave, html popovers, and selector delegatation.
2294 angular
.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
2295 .directive( 'popoverPopup', function () {
2299 scope
: { title
: '@', content
: '@', placement
: '@', animation
: '&', isOpen
: '&' },
2300 templateUrl
: 'template/popover/popover.html'
2303 .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile
, $timeout
, $parse
, $window
, $tooltip
) {
2304 return $tooltip( 'popover', 'popover', 'click' );
2308 angular
.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2310 .constant('progressConfig', {
2315 .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope
, $attrs
, progressConfig
, $transition
) {
2318 max
= angular
.isDefined($attrs
.max
) ? $scope
.$parent
.$eval($attrs
.max
) : progressConfig
.max
,
2319 animate
= angular
.isDefined($attrs
.animate
) ? $scope
.$parent
.$eval($attrs
.animate
) : progressConfig
.animate
;
2321 this.addBar = function(bar
, element
) {
2322 var oldValue
= 0, index
= bar
.$parent
.$index
;
2323 if ( angular
.isDefined(index
) && bars
[index
] ) {
2324 oldValue
= bars
[index
].value
;
2328 this.update(element
, bar
.value
, oldValue
);
2330 bar
.$watch('value', function(value
, oldValue
) {
2331 if (value
!== oldValue
) {
2332 self
.update(element
, value
, oldValue
);
2336 bar
.$on('$destroy', function() {
2337 self
.removeBar(bar
);
2341 // Update bar element width
2342 this.update = function(element
, newValue
, oldValue
) {
2343 var percent
= this.getPercentage(newValue
);
2346 element
.css('width', this.getPercentage(oldValue
) + '%');
2347 $transition(element
, {width
: percent
+ '%'});
2349 element
.css({'transition': 'none', 'width': percent
+ '%'});
2353 this.removeBar = function(bar
) {
2354 bars
.splice(bars
.indexOf(bar
), 1);
2357 this.getPercentage = function(value
) {
2358 return Math
.round(100 * value
/ max
);
2362 .directive('progress', function() {
2367 controller
: 'ProgressController',
2368 require
: 'progress',
2370 template
: '<div class="progress" ng-transclude></div>'
2371 //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
2375 .directive('bar', function() {
2380 require
: '^progress',
2385 templateUrl
: 'template/progressbar/bar.html',
2386 link: function(scope
, element
, attrs
, progressCtrl
) {
2387 progressCtrl
.addBar(scope
, element
);
2392 .directive('progressbar', function() {
2397 controller
: 'ProgressController',
2402 templateUrl
: 'template/progressbar/progressbar.html',
2403 link: function(scope
, element
, attrs
, progressCtrl
) {
2404 progressCtrl
.addBar(scope
, angular
.element(element
.children()[0]));
2408 angular
.module('ui.bootstrap.rating', [])
2410 .constant('ratingConfig', {
2416 .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope
, $attrs
, $parse
, ratingConfig
) {
2418 this.maxRange
= angular
.isDefined($attrs
.max
) ? $scope
.$parent
.$eval($attrs
.max
) : ratingConfig
.max
;
2419 this.stateOn
= angular
.isDefined($attrs
.stateOn
) ? $scope
.$parent
.$eval($attrs
.stateOn
) : ratingConfig
.stateOn
;
2420 this.stateOff
= angular
.isDefined($attrs
.stateOff
) ? $scope
.$parent
.$eval($attrs
.stateOff
) : ratingConfig
.stateOff
;
2422 this.createRateObjects = function(states
) {
2423 var defaultOptions
= {
2424 stateOn
: this.stateOn
,
2425 stateOff
: this.stateOff
2428 for (var i
= 0, n
= states
.length
; i
< n
; i
++) {
2429 states
[i
] = angular
.extend({ index
: i
}, defaultOptions
, states
[i
]);
2434 // Get objects used in template
2435 $scope
.range
= angular
.isDefined($attrs
.ratingStates
) ? this.createRateObjects(angular
.copy($scope
.$parent
.$eval($attrs
.ratingStates
))): this.createRateObjects(new Array(this.maxRange
));
2437 $scope
.rate = function(value
) {
2438 if ( $scope
.readonly
|| $scope
.value
=== value
) {
2442 $scope
.value
= value
;
2445 $scope
.enter = function(value
) {
2446 if ( ! $scope
.readonly
) {
2449 $scope
.onHover({value
: value
});
2452 $scope
.reset = function() {
2453 $scope
.val
= angular
.copy($scope
.value
);
2457 $scope
.$watch('value', function(value
) {
2461 $scope
.readonly
= false;
2462 if ($attrs
.readonly
) {
2463 $scope
.$parent
.$watch($parse($attrs
.readonly
), function(value
) {
2464 $scope
.readonly
= !!value
;
2469 .directive('rating', function() {
2477 controller
: 'RatingController',
2478 templateUrl
: 'template/rating/rating.html',
2485 * @name ui.bootstrap.tabs
2488 * AngularJS version of the tabs directive.
2491 angular
.module('ui.bootstrap.tabs', [])
2493 .controller('TabsetController', ['$scope', function TabsetCtrl($scope
) {
2495 tabs
= ctrl
.tabs
= $scope
.tabs
= [];
2497 ctrl
.select = function(tab
) {
2498 angular
.forEach(tabs
, function(tab
) {
2504 ctrl
.addTab
= function addTab(tab
) {
2506 if (tabs
.length
=== 1 || tab
.active
) {
2511 ctrl
.removeTab
= function removeTab(tab
) {
2512 var index
= tabs
.indexOf(tab
);
2513 //Select a new tab if the tab to be removed is selected
2514 if (tab
.active
&& tabs
.length
> 1) {
2515 //If this is the last tab, select the previous tab. else, the next tab.
2516 var newActiveIndex
= index
== tabs
.length
- 1 ? index
- 1 : index
+ 1;
2517 ctrl
.select(tabs
[newActiveIndex
]);
2519 tabs
.splice(index
, 1);
2525 * @name ui.bootstrap.tabs.directive:tabset
2529 * Tabset is the outer container for the tabs directive
2531 * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2532 * @param {boolean=} justified Whether or not to use justified styling for the tabs.
2535 <example module="ui.bootstrap">
2536 <file name="index.html">
2538 <tab heading="Tab 1"><b>First</b> Content!</tab>
2539 <tab heading="Tab 2"><i>Second</i> Content!</tab>
2542 <tabset vertical="true">
2543 <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
2544 <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
2546 <tabset justified="true">
2547 <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab>
2548 <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab>
2553 .directive('tabset', function() {
2559 controller
: 'TabsetController',
2560 templateUrl
: 'template/tabs/tabset.html',
2561 link: function(scope
, element
, attrs
) {
2562 scope
.vertical
= angular
.isDefined(attrs
.vertical
) ? scope
.$parent
.$eval(attrs
.vertical
) : false;
2563 scope
.justified
= angular
.isDefined(attrs
.justified
) ? scope
.$parent
.$eval(attrs
.justified
) : false;
2564 scope
.type
= angular
.isDefined(attrs
.type
) ? scope
.$parent
.$eval(attrs
.type
) : 'tabs';
2571 * @name ui.bootstrap.tabs.directive:tab
2574 * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
2575 * @param {string=} select An expression to evaluate when the tab is selected.
2576 * @param {boolean=} active A binding, telling whether or not this tab is selected.
2577 * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
2580 * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
2583 <example module="ui.bootstrap">
2584 <file name="index.html">
2585 <div ng-controller="TabsDemoCtrl">
2586 <button class="btn btn-small" ng-click="items[0].active = true">
2587 Select item 1, using active binding
2589 <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
2590 Enable/disable item 2, using disabled binding
2594 <tab heading="Tab 1">First Tab</tab>
2595 <tab select="alertMe()">
2596 <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
2597 Second Tab, with alert callback and html heading!
2599 <tab ng-repeat="item in items"
2600 heading="{{item.title}}"
2601 disabled="item.disabled"
2602 active="item.active">
2608 <file name="script.js">
2609 function TabsDemoCtrl($scope) {
2611 { title:"Dynamic Title 1", content:"Dynamic Item 0" },
2612 { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
2615 $scope.alertMe = function() {
2616 setTimeout(function() {
2617 alert("You've selected the alert tab!");
2627 * @name ui.bootstrap.tabs.directive:tabHeading
2631 * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
2634 <example module="ui.bootstrap">
2635 <file name="index.html">
2638 <tab-heading><b>HTML</b> in my titles?!</tab-heading>
2639 And some content, too!
2642 <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
2649 .directive('tab', ['$parse', function($parse
) {
2654 templateUrl
: 'template/tabs/tab.html',
2658 onSelect
: '&select', //This callback is called in contentHeadingTransclude
2659 //once it inserts the tab's content into the dom
2660 onDeselect
: '&deselect'
2662 controller: function() {
2663 //Empty controller so other directives can require being 'under' a tab
2665 compile: function(elm
, attrs
, transclude
) {
2666 return function postLink(scope
, elm
, attrs
, tabsetCtrl
) {
2667 var getActive
, setActive
;
2669 getActive
= $parse(attrs
.active
);
2670 setActive
= getActive
.assign
;
2671 scope
.$parent
.$watch(getActive
, function updateActive(value
, oldVal
) {
2672 // Avoid re-initializing scope.active as it is already initialized
2673 // below. (watcher is called async during init with value ===
2675 if (value
!== oldVal
) {
2676 scope
.active
= !!value
;
2679 scope
.active
= getActive(scope
.$parent
);
2681 setActive
= getActive
= angular
.noop
;
2684 scope
.$watch('active', function(active
) {
2685 // Note this watcher also initializes and assigns scope.active to the
2686 // attrs.active expression.
2687 setActive(scope
.$parent
, active
);
2689 tabsetCtrl
.select(scope
);
2696 scope
.disabled
= false;
2697 if ( attrs
.disabled
) {
2698 scope
.$parent
.$watch($parse(attrs
.disabled
), function(value
) {
2699 scope
.disabled
= !! value
;
2703 scope
.select = function() {
2704 if ( ! scope
.disabled
) {
2705 scope
.active
= true;
2709 tabsetCtrl
.addTab(scope
);
2710 scope
.$on('$destroy', function() {
2711 tabsetCtrl
.removeTab(scope
);
2715 //We need to transclude later, once the content container is ready.
2716 //when this link happens, we're inside a tab heading.
2717 scope
.$transcludeFn
= transclude
;
2723 .directive('tabHeadingTransclude', [function() {
2727 link: function(scope
, elm
, attrs
, tabCtrl
) {
2728 scope
.$watch('headingElement', function updateHeadingElement(heading
) {
2731 elm
.append(heading
);
2738 .directive('tabContentTransclude', function() {
2742 link: function(scope
, elm
, attrs
) {
2743 var tab
= scope
.$eval(attrs
.tabContentTransclude
);
2745 //Now our tab is ready to be transcluded: both the tab heading area
2746 //and the tab content area are loaded. Transclude 'em both.
2747 tab
.$transcludeFn(tab
.$parent
, function(contents
) {
2748 angular
.forEach(contents
, function(node
) {
2749 if (isTabHeading(node
)) {
2750 //Let tabHeadingTransclude know.
2751 tab
.headingElement
= node
;
2759 function isTabHeading(node
) {
2760 return node
.tagName
&& (
2761 node
.hasAttribute('tab-heading') ||
2762 node
.hasAttribute('data-tab-heading') ||
2763 node
.tagName
.toLowerCase() === 'tab-heading' ||
2764 node
.tagName
.toLowerCase() === 'data-tab-heading'
2771 angular
.module('ui.bootstrap.timepicker', [])
2773 .constant('timepickerConfig', {
2778 readonlyInput
: false,
2782 .directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse
, $log
, timepickerConfig
, $locale
) {
2785 require
:'?^ngModel',
2788 templateUrl
: 'template/timepicker/timepicker.html',
2789 link: function(scope
, element
, attrs
, ngModel
) {
2791 return; // do nothing if no ng-model
2794 var selected
= new Date(),
2795 meridians
= angular
.isDefined(attrs
.meridians
) ? scope
.$parent
.$eval(attrs
.meridians
) : timepickerConfig
.meridians
|| $locale
.DATETIME_FORMATS
.AMPMS
;
2797 var hourStep
= timepickerConfig
.hourStep
;
2798 if (attrs
.hourStep
) {
2799 scope
.$parent
.$watch($parse(attrs
.hourStep
), function(value
) {
2800 hourStep
= parseInt(value
, 10);
2804 var minuteStep
= timepickerConfig
.minuteStep
;
2805 if (attrs
.minuteStep
) {
2806 scope
.$parent
.$watch($parse(attrs
.minuteStep
), function(value
) {
2807 minuteStep
= parseInt(value
, 10);
2812 scope
.showMeridian
= timepickerConfig
.showMeridian
;
2813 if (attrs
.showMeridian
) {
2814 scope
.$parent
.$watch($parse(attrs
.showMeridian
), function(value
) {
2815 scope
.showMeridian
= !!value
;
2817 if ( ngModel
.$error
.time
) {
2818 // Evaluate from template
2819 var hours
= getHoursFromTemplate(), minutes
= getMinutesFromTemplate();
2820 if (angular
.isDefined( hours
) && angular
.isDefined( minutes
)) {
2821 selected
.setHours( hours
);
2830 // Get scope.hours in 24H mode if valid
2831 function getHoursFromTemplate ( ) {
2832 var hours
= parseInt( scope
.hours
, 10 );
2833 var valid
= ( scope
.showMeridian
) ? (hours
> 0 && hours
< 13) : (hours
>= 0 && hours
< 24);
2838 if ( scope
.showMeridian
) {
2839 if ( hours
=== 12 ) {
2842 if ( scope
.meridian
=== meridians
[1] ) {
2849 function getMinutesFromTemplate() {
2850 var minutes
= parseInt(scope
.minutes
, 10);
2851 return ( minutes
>= 0 && minutes
< 60 ) ? minutes
: undefined;
2854 function pad( value
) {
2855 return ( angular
.isDefined(value
) && value
.toString().length
< 2 ) ? '0' + value
: value
;
2859 var inputs
= element
.find('input'), hoursInputEl
= inputs
.eq(0), minutesInputEl
= inputs
.eq(1);
2861 // Respond on mousewheel spin
2862 var mousewheel
= (angular
.isDefined(attrs
.mousewheel
)) ? scope
.$eval(attrs
.mousewheel
) : timepickerConfig
.mousewheel
;
2865 var isScrollingUp = function(e
) {
2866 if (e
.originalEvent
) {
2867 e
= e
.originalEvent
;
2869 //pick correct delta variable depending on event
2870 var delta
= (e
.wheelDelta
) ? e
.wheelDelta
: -e
.deltaY
;
2871 return (e
.detail
|| delta
> 0);
2874 hoursInputEl
.bind('mousewheel wheel', function(e
) {
2875 scope
.$apply( (isScrollingUp(e
)) ? scope
.incrementHours() : scope
.decrementHours() );
2879 minutesInputEl
.bind('mousewheel wheel', function(e
) {
2880 scope
.$apply( (isScrollingUp(e
)) ? scope
.incrementMinutes() : scope
.decrementMinutes() );
2885 scope
.readonlyInput
= (angular
.isDefined(attrs
.readonlyInput
)) ? scope
.$eval(attrs
.readonlyInput
) : timepickerConfig
.readonlyInput
;
2886 if ( ! scope
.readonlyInput
) {
2888 var invalidate = function(invalidHours
, invalidMinutes
) {
2889 ngModel
.$setViewValue( null );
2890 ngModel
.$setValidity('time', false);
2891 if (angular
.isDefined(invalidHours
)) {
2892 scope
.invalidHours
= invalidHours
;
2894 if (angular
.isDefined(invalidMinutes
)) {
2895 scope
.invalidMinutes
= invalidMinutes
;
2899 scope
.updateHours = function() {
2900 var hours
= getHoursFromTemplate();
2902 if ( angular
.isDefined(hours
) ) {
2903 selected
.setHours( hours
);
2910 hoursInputEl
.bind('blur', function(e
) {
2911 if ( !scope
.validHours
&& scope
.hours
< 10) {
2912 scope
.$apply( function() {
2913 scope
.hours
= pad( scope
.hours
);
2918 scope
.updateMinutes = function() {
2919 var minutes
= getMinutesFromTemplate();
2921 if ( angular
.isDefined(minutes
) ) {
2922 selected
.setMinutes( minutes
);
2925 invalidate(undefined, true);
2929 minutesInputEl
.bind('blur', function(e
) {
2930 if ( !scope
.invalidMinutes
&& scope
.minutes
< 10 ) {
2931 scope
.$apply( function() {
2932 scope
.minutes
= pad( scope
.minutes
);
2937 scope
.updateHours
= angular
.noop
;
2938 scope
.updateMinutes
= angular
.noop
;
2941 ngModel
.$render = function() {
2942 var date
= ngModel
.$modelValue
? new Date( ngModel
.$modelValue
) : null;
2944 if ( isNaN(date
) ) {
2945 ngModel
.$setValidity('time', false);
2946 $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.');
2956 // Call internally when we know that model is valid.
2957 function refresh( keyboardChange
) {
2959 ngModel
.$setViewValue( new Date(selected
) );
2960 updateTemplate( keyboardChange
);
2963 function makeValid() {
2964 ngModel
.$setValidity('time', true);
2965 scope
.invalidHours
= false;
2966 scope
.invalidMinutes
= false;
2969 function updateTemplate( keyboardChange
) {
2970 var hours
= selected
.getHours(), minutes
= selected
.getMinutes();
2972 if ( scope
.showMeridian
) {
2973 hours
= ( hours
=== 0 || hours
=== 12 ) ? 12 : hours
% 12; // Convert 24 to 12 hour system
2975 scope
.hours
= keyboardChange
=== 'h' ? hours
: pad(hours
);
2976 scope
.minutes
= keyboardChange
=== 'm' ? minutes
: pad(minutes
);
2977 scope
.meridian
= selected
.getHours() < 12 ? meridians
[0] : meridians
[1];
2980 function addMinutes( minutes
) {
2981 var dt
= new Date( selected
.getTime() + minutes
* 60000 );
2982 selected
.setHours( dt
.getHours(), dt
.getMinutes() );
2986 scope
.incrementHours = function() {
2987 addMinutes( hourStep
* 60 );
2989 scope
.decrementHours = function() {
2990 addMinutes( - hourStep
* 60 );
2992 scope
.incrementMinutes = function() {
2993 addMinutes( minuteStep
);
2995 scope
.decrementMinutes = function() {
2996 addMinutes( - minuteStep
);
2998 scope
.toggleMeridian = function() {
2999 addMinutes( 12 * 60 * (( selected
.getHours() < 12 ) ? 1 : -1) );
3005 angular
.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3008 * A helper service that can parse typeahead's syntax (string provided by users)
3009 * Extracted to a separate service for ease of unit testing
3011 .factory('typeaheadParser', ['$parse', function ($parse
) {
3013 // 00000111000000000000022200000000000000003333333333333330000000000044000
3014 var TYPEAHEAD_REGEXP
= /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
3017 parse:function (input
) {
3019 var match
= input
.match(TYPEAHEAD_REGEXP
), modelMapper
, viewMapper
, source
;
3022 "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
3023 " but got '" + input
+ "'.");
3028 source
:$parse(match
[4]),
3029 viewMapper
:$parse(match
[2] || match
[1]),
3030 modelMapper
:$parse(match
[1])
3036 .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
3037 function ($compile
, $parse
, $q
, $timeout
, $document
, $position
, typeaheadParser
) {
3039 var HOT_KEYS
= [9, 13, 27, 38, 40];
3043 link:function (originalScope
, element
, attrs
, modelCtrl
) {
3045 //SUPPORTED ATTRIBUTES (OPTIONS)
3047 //minimal no of characters that needs to be entered before typeahead kicks-in
3048 var minSearch
= originalScope
.$eval(attrs
.typeaheadMinLength
) || 1;
3050 //minimal wait time after last character typed before typehead kicks-in
3051 var waitTime
= originalScope
.$eval(attrs
.typeaheadWaitMs
) || 0;
3053 //should it restrict model values to the ones selected from the popup only?
3054 var isEditable
= originalScope
.$eval(attrs
.typeaheadEditable
) !== false;
3056 //binding to a variable that indicates if matches are being retrieved asynchronously
3057 var isLoadingSetter
= $parse(attrs
.typeaheadLoading
).assign
|| angular
.noop
;
3059 //a callback executed when a match is selected
3060 var onSelectCallback
= $parse(attrs
.typeaheadOnSelect
);
3062 var inputFormatter
= attrs
.typeaheadInputFormatter
? $parse(attrs
.typeaheadInputFormatter
) : undefined;
3064 var appendToBody
= attrs
.typeaheadAppendToBody
? $parse(attrs
.typeaheadAppendToBody
) : false;
3066 //INTERNAL VARIABLES
3068 //model setter executed upon match selection
3069 var $setModelValue
= $parse(attrs
.ngModel
).assign
;
3071 //expressions used by typeahead
3072 var parserResult
= typeaheadParser
.parse(attrs
.typeahead
);
3076 //pop-up element used to display matches
3077 var popUpEl
= angular
.element('<div typeahead-popup></div>');
3080 active
: 'activeIdx',
3081 select
: 'select(activeIdx)',
3083 position
: 'position'
3085 //custom item template
3086 if (angular
.isDefined(attrs
.typeaheadTemplateUrl
)) {
3087 popUpEl
.attr('template-url', attrs
.typeaheadTemplateUrl
);
3090 //create a child scope for the typeahead directive so we are not polluting original scope
3091 //with typeahead-specific data (matches, query etc.)
3092 var scope
= originalScope
.$new();
3093 originalScope
.$on('$destroy', function(){
3097 var resetMatches = function() {
3099 scope
.activeIdx
= -1;
3102 var getMatchesAsync = function(inputValue
) {
3104 var locals
= {$viewValue
: inputValue
};
3105 isLoadingSetter(originalScope
, true);
3106 $q
.when(parserResult
.source(originalScope
, locals
)).then(function(matches
) {
3108 //it might happen that several async queries were in progress if a user were typing fast
3109 //but we are interested only in responses that correspond to the current view value
3110 if (inputValue
=== modelCtrl
.$viewValue
&& hasFocus
) {
3111 if (matches
.length
> 0) {
3113 scope
.activeIdx
= 0;
3114 scope
.matches
.length
= 0;
3117 for(var i
=0; i
<matches
.length
; i
++) {
3118 locals
[parserResult
.itemName
] = matches
[i
];
3119 scope
.matches
.push({
3120 label
: parserResult
.viewMapper(scope
, locals
),
3125 scope
.query
= inputValue
;
3126 //position pop-up with matches - we need to re-calculate its position each time we are opening a window
3127 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
3128 //due to other elements being rendered
3129 scope
.position
= appendToBody
? $position
.offset(element
) : $position
.position(element
);
3130 scope
.position
.top
= scope
.position
.top
+ element
.prop('offsetHeight');
3135 isLoadingSetter(originalScope
, false);
3139 isLoadingSetter(originalScope
, false);
3145 //we need to propagate user's query so we can higlight matches
3146 scope
.query
= undefined;
3148 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3151 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
3152 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
3153 modelCtrl
.$parsers
.unshift(function (inputValue
) {
3157 if (inputValue
&& inputValue
.length
>= minSearch
) {
3159 if (timeoutPromise
) {
3160 $timeout
.cancel(timeoutPromise
);//cancel previous timeout
3162 timeoutPromise
= $timeout(function () {
3163 getMatchesAsync(inputValue
);
3166 getMatchesAsync(inputValue
);
3169 isLoadingSetter(originalScope
, false);
3177 // Reset in case user had typed something previously.
3178 modelCtrl
.$setValidity('editable', true);
3181 modelCtrl
.$setValidity('editable', false);
3187 modelCtrl
.$formatters
.push(function (modelValue
) {
3189 var candidateViewValue
, emptyViewValue
;
3192 if (inputFormatter
) {
3194 locals
['$model'] = modelValue
;
3195 return inputFormatter(originalScope
, locals
);
3199 //it might happen that we don't have enough info to properly render input value
3200 //we need to check for this situation and simply return model value if we can't apply custom formatting
3201 locals
[parserResult
.itemName
] = modelValue
;
3202 candidateViewValue
= parserResult
.viewMapper(originalScope
, locals
);
3203 locals
[parserResult
.itemName
] = undefined;
3204 emptyViewValue
= parserResult
.viewMapper(originalScope
, locals
);
3206 return candidateViewValue
!== emptyViewValue
? candidateViewValue
: modelValue
;
3210 scope
.select = function (activeIdx
) {
3211 //called from within the $digest() cycle
3215 locals
[parserResult
.itemName
] = item
= scope
.matches
[activeIdx
].model
;
3216 model
= parserResult
.modelMapper(originalScope
, locals
);
3217 $setModelValue(originalScope
, model
);
3218 modelCtrl
.$setValidity('editable', true);
3220 onSelectCallback(originalScope
, {
3223 $label
: parserResult
.viewMapper(originalScope
, locals
)
3228 //return focus to the input element if a mach was selected via a mouse click event
3232 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
3233 element
.bind('keydown', function (evt
) {
3235 //typeahead is open and an "interesting" key was pressed
3236 if (scope
.matches
.length
=== 0 || HOT_KEYS
.indexOf(evt
.which
) === -1) {
3240 evt
.preventDefault();
3242 if (evt
.which
=== 40) {
3243 scope
.activeIdx
= (scope
.activeIdx
+ 1) % scope
.matches
.length
;
3246 } else if (evt
.which
=== 38) {
3247 scope
.activeIdx
= (scope
.activeIdx
? scope
.activeIdx
: scope
.matches
.length
) - 1;
3250 } else if (evt
.which
=== 13 || evt
.which
=== 9) {
3251 scope
.$apply(function () {
3252 scope
.select(scope
.activeIdx
);
3255 } else if (evt
.which
=== 27) {
3256 evt
.stopPropagation();
3263 element
.bind('blur', function (evt
) {
3267 // Keep reference to click handler to unbind it.
3268 var dismissClickHandler = function (evt
) {
3269 if (element
[0] !== evt
.target
) {
3275 $document
.bind('click', dismissClickHandler
);
3277 originalScope
.$on('$destroy', function(){
3278 $document
.unbind('click', dismissClickHandler
);
3281 var $popup
= $compile(popUpEl
)(scope
);
3282 if ( appendToBody
) {
3283 $document
.find('body').append($popup
);
3285 element
.after($popup
);
3292 .directive('typeaheadPopup', function () {
3303 templateUrl
:'template/typeahead/typeahead-popup.html',
3304 link:function (scope
, element
, attrs
) {
3306 scope
.templateUrl
= attrs
.templateUrl
;
3308 scope
.isOpen = function () {
3309 return scope
.matches
.length
> 0;
3312 scope
.isActive = function (matchIdx
) {
3313 return scope
.active
== matchIdx
;
3316 scope
.selectActive = function (matchIdx
) {
3317 scope
.active
= matchIdx
;
3320 scope
.selectMatch = function (activeIdx
) {
3321 scope
.select({activeIdx
:activeIdx
});
3327 .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http
, $templateCache
, $compile
, $parse
) {
3335 link:function (scope
, element
, attrs
) {
3336 var tplUrl
= $parse(attrs
.templateUrl
)(scope
.$parent
) || 'template/typeahead/typeahead-match.html';
3337 $http
.get(tplUrl
, {cache
: $templateCache
}).success(function(tplContent
){
3338 element
.replaceWith($compile(tplContent
.trim())(scope
));
3344 .filter('typeaheadHighlight', function() {
3346 function escapeRegexp(queryToEscape
) {
3347 return queryToEscape
.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
3350 return function(matchItem
, query
) {
3351 return query
? matchItem
.replace(new RegExp(escapeRegexp(query
), 'gi'), '<strong>$&</strong>') : matchItem
;
3354 angular
.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache
) {
3355 $templateCache
.put("template/accordion/accordion-group.html",
3356 "<div class=\"panel panel-default\">\n" +
3357 " <div class=\"panel-heading\">\n" +
3358 " <h4 class=\"panel-title\">\n" +
3359 " <a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a>\n" +
3362 " <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" +
3363 " <div class=\"panel-body\" ng-transclude></div>\n" +
3368 angular
.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache
) {
3369 $templateCache
.put("template/accordion/accordion.html",
3370 "<div class=\"panel-group\" ng-transclude></div>");
3373 angular
.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache
) {
3374 $templateCache
.put("template/alert/alert.html",
3375 "<div class='alert' ng-class='\"alert-\" + (type || \"warning\")'>\n" +
3376 " <button ng-show='closeable' type='button' class='close' ng-click='close()'>×</button>\n" +
3377 " <div ng-transclude></div>\n" +
3382 angular
.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache
) {
3383 $templateCache
.put("template/carousel/carousel.html",
3384 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\">\n" +
3385 " <ol class=\"carousel-indicators\" ng-show=\"slides().length > 1\">\n" +
3386 " <li ng-repeat=\"slide in slides()\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
3388 " <div class=\"carousel-inner\" ng-transclude></div>\n" +
3389 " <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides().length > 1\"><span class=\"icon-prev\"></span></a>\n" +
3390 " <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides().length > 1\"><span class=\"icon-next\"></span></a>\n" +
3395 angular
.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache
) {
3396 $templateCache
.put("template/carousel/slide.html",
3397 "<div ng-class=\"{\n" +
3398 " 'active': leaving || (active && !entering),\n" +
3399 " 'prev': (next || active) && direction=='prev',\n" +
3400 " 'next': (next || active) && direction=='next',\n" +
3401 " 'right': direction=='prev',\n" +
3402 " 'left': direction=='next'\n" +
3403 " }\" class=\"item text-center\" ng-transclude></div>\n" +
3407 angular
.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache
) {
3408 $templateCache
.put("template/datepicker/datepicker.html",
3412 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
3413 " <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button type=\"button\" class=\"btn btn-default btn-sm btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
3414 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
3416 " <tr ng-show=\"labels.length > 0\" class=\"h6\">\n" +
3417 " <th ng-show=\"showWeekNumbers\" class=\"text-center\">#</th>\n" +
3418 " <th ng-repeat=\"label in labels\" class=\"text-center\">{{label}}</th>\n" +
3422 " <tr ng-repeat=\"row in rows\">\n" +
3423 " <td ng-show=\"showWeekNumbers\" class=\"text-center\"><em>{{ getWeekNumber(row) }}</em></td>\n" +
3424 " <td ng-repeat=\"dt in row\" class=\"text-center\">\n" +
3425 " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{'text-muted': dt.secondary}\">{{dt.label}}</span></button>\n" +
3433 angular
.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache
) {
3434 $templateCache
.put("template/datepicker/popup.html",
3435 "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3436 " <li ng-transclude></li>\n" +
3437 " <li ng-show=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
3438 " <span class=\"btn-group\">\n" +
3439 " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"today()\">{{currentText}}</button>\n" +
3440 " <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"showWeeks = ! showWeeks\" ng-class=\"{active: showWeeks}\">{{toggleWeeksText}}</button>\n" +
3441 " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"clear()\">{{clearText}}</button>\n" +
3443 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"isOpen = false\">{{closeText}}</button>\n" +
3449 angular
.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache
) {
3450 $templateCache
.put("template/modal/backdrop.html",
3451 "<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1040 + index*10}\"></div>");
3454 angular
.module("template/modal/window.html", []).run(["$templateCache", function($templateCache
) {
3455 $templateCache
.put("template/modal/window.html",
3456 "<div tabindex=\"-1\" class=\"modal fade {{ windowClass }}\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
3457 " <div class=\"modal-dialog\"><div class=\"modal-content\" ng-transclude></div></div>\n" +
3461 angular
.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache
) {
3462 $templateCache
.put("template/pagination/pager.html",
3463 "<ul class=\"pager\">\n" +
3464 " <li ng-repeat=\"page in pages\" ng-class=\"{disabled: page.disabled, previous: page.previous, next: page.next}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3468 angular
.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache
) {
3469 $templateCache
.put("template/pagination/pagination.html",
3470 "<ul class=\"pagination\">\n" +
3471 " <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3475 angular
.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache
) {
3476 $templateCache
.put("template/tooltip/tooltip-html-unsafe-popup.html",
3477 "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
3478 " <div class=\"tooltip-arrow\"></div>\n" +
3479 " <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" +
3484 angular
.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache
) {
3485 $templateCache
.put("template/tooltip/tooltip-popup.html",
3486 "<div class=\"tooltip {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
3487 " <div class=\"tooltip-arrow\"></div>\n" +
3488 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" +
3493 angular
.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache
) {
3494 $templateCache
.put("template/popover/popover.html",
3495 "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen(), fade: animation() }\">\n" +
3496 " <div class=\"arrow\"></div>\n" +
3498 " <div class=\"popover-inner\">\n" +
3499 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" +
3500 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" +
3506 angular
.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache
) {
3507 $templateCache
.put("template/progressbar/bar.html",
3508 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div>");
3511 angular
.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache
) {
3512 $templateCache
.put("template/progressbar/progress.html",
3513 "<div class=\"progress\" ng-transclude></div>");
3516 angular
.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache
) {
3517 $templateCache
.put("template/progressbar/progressbar.html",
3518 "<div class=\"progress\"><div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div></div>");
3521 angular
.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache
) {
3522 $templateCache
.put("template/rating/rating.html",
3523 "<span ng-mouseleave=\"reset()\">\n" +
3524 " <i ng-repeat=\"r in range\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < val && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\"></i>\n" +
3528 angular
.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache
) {
3529 $templateCache
.put("template/tabs/tab.html",
3530 "<li ng-class=\"{active: active, disabled: disabled}\">\n" +
3531 " <a ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" +
3536 angular
.module("template/tabs/tabset-titles.html", []).run(["$templateCache", function($templateCache
) {
3537 $templateCache
.put("template/tabs/tabset-titles.html",
3538 "<ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical}\">\n" +
3543 angular
.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache
) {
3544 $templateCache
.put("template/tabs/tabset.html",
3546 "<div class=\"tabbable\">\n" +
3547 " <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
3548 " <div class=\"tab-content\">\n" +
3549 " <div class=\"tab-pane\" \n" +
3550 " ng-repeat=\"tab in tabs\" \n" +
3551 " ng-class=\"{active: tab.active}\"\n" +
3552 " tab-content-transclude=\"tab\">\n" +
3559 angular
.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache
) {
3560 $templateCache
.put("template/timepicker/timepicker.html",
3563 " <tr class=\"text-center\">\n" +
3564 " <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
3565 " <td> </td>\n" +
3566 " <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" +
3567 " <td ng-show=\"showMeridian\"></td>\n" +
3570 " <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" +
3571 " <input type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-mousewheel=\"incrementHours()\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
3574 " <td style=\"width:50px;\" class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" +
3575 " <input type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" +
3577 " <td ng-show=\"showMeridian\"><button class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" +
3579 " <tr class=\"text-center\">\n" +
3580 " <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
3581 " <td> </td>\n" +
3582 " <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" +
3583 " <td ng-show=\"showMeridian\"></td>\n" +
3590 angular
.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache
) {
3591 $templateCache
.put("template/typeahead/typeahead-match.html",
3592 "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>");
3595 angular
.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache
) {
3596 $templateCache
.put("template/typeahead/typeahead-popup.html",
3597 "<ul class=\"dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3598 " <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
3599 " <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +