c3c61772993399e799108d80eb51e5fa12871317
[civicrm-core.git] / js / ui-bootstrap-tpls-0.9.0.js
1 /**
2 * Created by aditya on 6/13/14.
3 */
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', [])
7
8 /**
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.
16 */
17 .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
18
19 var $transition = function(element, trigger, options) {
20 options = options || {};
21 var deferred = $q.defer();
22 var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
23
24 var transitionEndHandler = function(event) {
25 $rootScope.$apply(function() {
26 element.unbind(endEventName, transitionEndHandler);
27 deferred.resolve(element);
28 });
29 };
30
31 if (endEventName) {
32 element.bind(endEventName, transitionEndHandler);
33 }
34
35 // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
36 $timeout(function() {
37 if ( angular.isString(trigger) ) {
38 element.addClass(trigger);
39 } else if ( angular.isFunction(trigger) ) {
40 trigger(element);
41 } else if ( angular.isObject(trigger) ) {
42 element.css(trigger);
43 }
44 //If browser does not support transitions, instantly resolve
45 if ( !endEventName ) {
46 deferred.resolve(element);
47 }
48 });
49
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() {
54 if ( endEventName ) {
55 element.unbind(endEventName, transitionEndHandler);
56 }
57 deferred.reject('Transition cancelled');
58 };
59
60 return deferred.promise;
61 };
62
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'
70 };
71 var animationEndEventNames = {
72 'WebkitTransition': 'webkitAnimationEnd',
73 'MozTransition': 'animationend',
74 'OTransition': 'oAnimationEnd',
75 'transition': 'animationend'
76 };
77 function findEndEventName(endEventNames) {
78 for (var name in endEventNames){
79 if (transElement.style[name] !== undefined) {
80 return endEventNames[name];
81 }
82 }
83 }
84 $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
85 $transition.animationEndEventName = findEndEventName(animationEndEventNames);
86 return $transition;
87 }]);
88
89 angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
90
91 .directive('collapse', ['$transition', function ($transition, $timeout) {
92
93 return {
94 link: function (scope, element, attrs) {
95
96 var initialAnimSkip = true;
97 var currentTransition;
98
99 function doTransition(change) {
100 var newTransition = $transition(element, change);
101 if (currentTransition) {
102 currentTransition.cancel();
103 }
104 currentTransition = newTransition;
105 newTransition.then(newTransitionDone, newTransitionDone);
106 return newTransition;
107
108 function newTransitionDone() {
109 // Make sure it's this transition, otherwise, leave it alone.
110 if (currentTransition === newTransition) {
111 currentTransition = undefined;
112 }
113 }
114 }
115
116 function expand() {
117 if (initialAnimSkip) {
118 initialAnimSkip = false;
119 expandDone();
120 } else {
121 element.removeClass('collapse').addClass('collapsing');
122 doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
123 }
124 }
125
126 function expandDone() {
127 element.removeClass('collapsing');
128 element.addClass('collapse in');
129 element.css({height: 'auto'});
130 }
131
132 function collapse() {
133 if (initialAnimSkip) {
134 initialAnimSkip = false;
135 collapseDone();
136 element.css({height: 0});
137 } else {
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;
142
143 element.removeClass('collapse in').addClass('collapsing');
144
145 doTransition({ height: 0 }).then(collapseDone);
146 }
147 }
148
149 function collapseDone() {
150 element.removeClass('collapsing');
151 element.addClass('collapse');
152 }
153
154 scope.$watch(attrs.collapse, function (shouldCollapse) {
155 if (shouldCollapse) {
156 collapse();
157 } else {
158 expand();
159 }
160 });
161 }
162 };
163 }]);
164
165 angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
166
167 .constant('accordionConfig', {
168 closeOthers: true
169 })
170
171 .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
172
173 // This array keeps track of the accordion groups
174 this.groups = [];
175
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;
179 if ( closeOthers ) {
180 angular.forEach(this.groups, function (group) {
181 if ( group !== openGroup ) {
182 group.isOpen = false;
183 }
184 });
185 }
186 };
187
188 // This is called from the accordion-group directive to add itself to the accordion
189 this.addGroup = function(groupScope) {
190 var that = this;
191 this.groups.push(groupScope);
192
193 groupScope.$on('$destroy', function (event) {
194 that.removeGroup(groupScope);
195 });
196 };
197
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);
203 }
204 };
205
206 }])
207
208 // The accordion directive simply sets up the directive controller
209 // and adds an accordion CSS class to itself element.
210 .directive('accordion', function () {
211 return {
212 restrict:'EA',
213 controller:'AccordionController',
214 transclude: true,
215 replace: false,
216 templateUrl: 'template/accordion/accordion.html'
217 };
218 })
219
220 // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
221 .directive('accordionGroup', ['$parse', function($parse) {
222 return {
223 require:'^accordion', // We need this directive to be inside an accordion
224 restrict:'EA',
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;
232 };
233 },
234 link: function(scope, element, attrs, accordionCtrl) {
235 var getIsOpen, setIsOpen;
236
237 accordionCtrl.addGroup(scope);
238
239 scope.isOpen = false;
240
241 if ( attrs.isOpen ) {
242 getIsOpen = $parse(attrs.isOpen);
243 setIsOpen = getIsOpen.assign;
244
245 scope.$parent.$watch(getIsOpen, function(value) {
246 scope.isOpen = !!value;
247 });
248 }
249
250 scope.$watch('isOpen', function(value) {
251 if ( value ) {
252 accordionCtrl.closeOthers(scope);
253 }
254 if ( setIsOpen ) {
255 setIsOpen(scope.$parent, value);
256 }
257 });
258 }
259 };
260 }])
261
262 // Use accordion-heading below an accordion-group to provide a heading containing HTML
263 // <accordion-group>
264 // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
265 // </accordion-group>
266 .directive('accordionHeading', function() {
267 return {
268 restrict: 'EA',
269 transclude: true, // Grab the contents to be used as the heading
270 template: '', // In effect remove this element!
271 replace: true,
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() {}));
279 };
280 }
281 };
282 })
283
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>
288 // ...
289 // </div>
290 .directive('accordionTransclude', function() {
291 return {
292 require: '^accordionGroup',
293 link: function(scope, element, attr, controller) {
294 scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
295 if ( heading ) {
296 element.html('');
297 element.append(heading);
298 }
299 });
300 }
301 };
302 });
303
304 angular.module("ui.bootstrap.alert", [])
305
306 .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
307 $scope.closeable = 'close' in $attrs;
308 }])
309
310 .directive('alert', function () {
311 return {
312 restrict:'EA',
313 controller:'AlertController',
314 templateUrl:'template/alert/alert.html',
315 transclude:true,
316 replace:true,
317 scope: {
318 type: '=',
319 close: '&'
320 }
321 };
322 });
323
324 angular.module('ui.bootstrap.bindHtml', [])
325
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 || '');
331 });
332 };
333 });
334 angular.module('ui.bootstrap.buttons', [])
335
336 .constant('buttonConfig', {
337 activeClass: 'active',
338 toggleEvent: 'click'
339 })
340
341 .controller('ButtonsController', ['buttonConfig', function(buttonConfig) {
342 this.activeClass = buttonConfig.activeClass || 'active';
343 this.toggleEvent = buttonConfig.toggleEvent || 'click';
344 }])
345
346 .directive('btnRadio', function () {
347 return {
348 require: ['btnRadio', 'ngModel'],
349 controller: 'ButtonsController',
350 link: function (scope, element, attrs, ctrls) {
351 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
352
353 //model -> UI
354 ngModelCtrl.$render = function () {
355 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
356 };
357
358 //ui->model
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();
364 });
365 }
366 });
367 }
368 };
369 })
370
371 .directive('btnCheckbox', function () {
372 return {
373 require: ['btnCheckbox', 'ngModel'],
374 controller: 'ButtonsController',
375 link: function (scope, element, attrs, ctrls) {
376 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
377
378 function getTrueValue() {
379 return getCheckboxValue(attrs.btnCheckboxTrue, true);
380 }
381
382 function getFalseValue() {
383 return getCheckboxValue(attrs.btnCheckboxFalse, false);
384 }
385
386 function getCheckboxValue(attributeValue, defaultValue) {
387 var val = scope.$eval(attributeValue);
388 return angular.isDefined(val) ? val : defaultValue;
389 }
390
391 //model -> UI
392 ngModelCtrl.$render = function () {
393 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
394 };
395
396 //ui->model
397 element.bind(buttonsCtrl.toggleEvent, function () {
398 scope.$apply(function () {
399 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
400 ngModelCtrl.$render();
401 });
402 });
403 }
404 };
405 });
406
407 /**
408 * @ngdoc overview
409 * @name ui.bootstrap.carousel
410 *
411 * @description
412 * AngularJS version of an image carousel.
413 *
414 */
415 angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
416 .controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
417 var self = this,
418 slides = self.slides = [],
419 currentIndex = -1,
420 currentTimeout, isPlaying;
421 self.currentSlide = null;
422
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";
430 }
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
435 $timeout(goNext);
436 } else {
437 goNext();
438 }
439 }
440 function goNext() {
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
448
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});
452 });
453 angular.extend(nextSlide, {direction: direction, active: true, entering: true});
454 angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
455
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); }
462 );
463 }(nextSlide, self.currentSlide));
464 } else {
465 transitionDone(nextSlide, self.currentSlide);
466 }
467 self.currentSlide = nextSlide;
468 currentIndex = nextIndex;
469 //every time you change slides, reset the timer
470 restartTimer();
471 }
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;
476 }
477 };
478 $scope.$on('$destroy', function () {
479 destroyed = true;
480 });
481
482 /* Allow outside people to call indexOf on slides array */
483 self.indexOfSlide = function(slide) {
484 return slides.indexOf(slide);
485 };
486
487 $scope.next = function() {
488 var newIndex = (currentIndex + 1) % slides.length;
489
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');
493 }
494 };
495
496 $scope.prev = function() {
497 var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
498
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');
502 }
503 };
504
505 $scope.select = function(slide) {
506 self.select(slide);
507 };
508
509 $scope.isActive = function(slide) {
510 return self.currentSlide === slide;
511 };
512
513 $scope.slides = function() {
514 return slides;
515 };
516
517 $scope.$watch('interval', restartTimer);
518 $scope.$on('$destroy', resetTimer);
519
520 function restartTimer() {
521 resetTimer();
522 var interval = +$scope.interval;
523 if (!isNaN(interval) && interval>=0) {
524 currentTimeout = $timeout(timerFn, interval);
525 }
526 }
527
528 function resetTimer() {
529 if (currentTimeout) {
530 $timeout.cancel(currentTimeout);
531 currentTimeout = null;
532 }
533 }
534
535 function timerFn() {
536 if (isPlaying) {
537 $scope.next();
538 restartTimer();
539 } else {
540 $scope.pause();
541 }
542 }
543
544 $scope.play = function() {
545 if (!isPlaying) {
546 isPlaying = true;
547 restartTimer();
548 }
549 };
550 $scope.pause = function() {
551 if (!$scope.noPause) {
552 isPlaying = false;
553 resetTimer();
554 }
555 };
556
557 self.addSlide = function(slide, element) {
558 slide.$element = element;
559 slides.push(slide);
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) {
564 $scope.play();
565 }
566 } else {
567 slide.active = false;
568 }
569 };
570
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]);
578 } else {
579 self.select(slides[index]);
580 }
581 } else if (currentIndex > index) {
582 currentIndex--;
583 }
584 };
585
586 }])
587
588 /**
589 * @ngdoc directive
590 * @name ui.bootstrap.carousel.directive:carousel
591 * @restrict EA
592 *
593 * @description
594 * Carousel is the outer container for a set of image 'slides' to showcase.
595 *
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).
599 *
600 * @example
601 <example module="ui.bootstrap">
602 <file name="index.html">
603 <carousel>
604 <slide>
605 <img src="http://placekitten.com/150/150" style="margin:auto;">
606 <div class="carousel-caption">
607 <p>Beautiful!</p>
608 </div>
609 </slide>
610 <slide>
611 <img src="http://placekitten.com/100/150" style="margin:auto;">
612 <div class="carousel-caption">
613 <p>D'aww!</p>
614 </div>
615 </slide>
616 </carousel>
617 </file>
618 <file name="demo.css">
619 .carousel-indicators {
620 top: auto;
621 bottom: 15px;
622 }
623 </file>
624 </example>
625 */
626 .directive('carousel', [function() {
627 return {
628 restrict: 'EA',
629 transclude: true,
630 replace: true,
631 controller: 'CarouselController',
632 require: 'carousel',
633 templateUrl: 'template/carousel/carousel.html',
634 scope: {
635 interval: '=',
636 noTransition: '=',
637 noPause: '='
638 }
639 };
640 }])
641
642 /**
643 * @ngdoc directive
644 * @name ui.bootstrap.carousel.directive:slide
645 * @restrict EA
646 *
647 * @description
648 * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
649 *
650 * @param {boolean=} active Model binding, whether or not this slide is currently active.
651 *
652 * @example
653 <example module="ui.bootstrap">
654 <file name="index.html">
655 <div ng-controller="CarouselDemoCtrl">
656 <carousel>
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>
662 </div>
663 </slide>
664 </carousel>
665 <div class="row-fluid">
666 <div class="span6">
667 <ul>
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}}
671 </li>
672 </ul>
673 <a class="btn" ng-click="addSlide()">Add Slide</a>
674 </div>
675 <div class="span6">
676 Interval, in milliseconds: <input type="number" ng-model="myInterval">
677 <br />Enter a negative number to stop the interval.
678 </div>
679 </div>
680 </div>
681 </file>
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);
688 slides.push({
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]
692 });
693 };
694 for (var i=0; i<4; i++) $scope.addSlide();
695 }
696 </file>
697 <file name="demo.css">
698 .carousel-indicators {
699 top: auto;
700 bottom: 15px;
701 }
702 </file>
703 </example>
704 */
705
706 .directive('slide', ['$parse', function($parse) {
707 return {
708 require: '^carousel',
709 restrict: 'EA',
710 transclude: true,
711 replace: true,
712 templateUrl: 'template/carousel/slide.html',
713 scope: {
714 },
715 link: function (scope, element, attrs, carouselCtrl) {
716 //Set up optional 'active' = binding
717 if (attrs.active) {
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);
723
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;
729 } else {
730 // if the parent can be assigned then do so
731 setActive(scope.$parent, parentActive = lastValue = scope.active);
732 }
733 }
734 return parentActive;
735 });
736 }
737
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);
742 });
743
744 scope.$watch('active', function(active) {
745 if (active) {
746 carouselCtrl.select(scope);
747 }
748 });
749 }
750 };
751 }]);
752
753 angular.module('ui.bootstrap.position', [])
754
755 /**
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.).
760 */
761 .factory('$position', ['$document', '$window', function ($document, $window) {
762
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];
768 }
769 // finally try and get inline style
770 return el.style[cssprop];
771 }
772
773 /**
774 * Checks if a given element is statically positioned
775 * @param element - raw DOM element
776 */
777 function isStaticPositioned(element) {
778 return (getStyle(element, "position") || 'static' ) === 'static';
779 }
780
781 /**
782 * returns the closest, non-statically positioned parentOffset of a given element
783 * @param element
784 */
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;
790 }
791 return offsetParent || docDomEl;
792 };
793
794 return {
795 /**
796 * Provides read-only equivalent of jQuery's position function:
797 * http://api.jquery.com/position/
798 */
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;
807 }
808
809 var boundingClientRect = element[0].getBoundingClientRect();
810 return {
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
815 };
816 },
817
818 /**
819 * Provides read-only equivalent of jQuery's offset function:
820 * http://api.jquery.com/offset/
821 */
822 offset: function (element) {
823 var boundingClientRect = element[0].getBoundingClientRect();
824 return {
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)
829 };
830 }
831 };
832 }]);
833
834 angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
835
836 .constant('datepickerConfig', {
837 dayFormat: 'dd',
838 monthFormat: 'MMMM',
839 yearFormat: 'yyyy',
840 dayHeaderFormat: 'EEE',
841 dayTitleFormat: 'MMMM yyyy',
842 monthTitleFormat: 'yyyy',
843 showWeeks: true,
844 startingDay: 0,
845 yearRange: 20,
846 minDate: null,
847 maxDate: null
848 })
849
850 .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
851 var format = {
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)
858 },
859 startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
860 yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
861
862 this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
863 this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
864
865 function getValue(value, defaultValue) {
866 return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
867 }
868
869 function getDaysInMonth( year, month ) {
870 return new Date(year, month, 0).getDate();
871 }
872
873 function getDates(startDate, n) {
874 var dates = new Array(n);
875 var current = startDate, i = 0;
876 while (i < n) {
877 dates[i++] = new Date(current);
878 current.setDate( current.getDate() + 1 );
879 }
880 return dates;
881 }
882
883 function makeDate(date, format, isSelected, isSecondary) {
884 return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
885 }
886
887 this.modes = [
888 {
889 name: 'day',
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;
895
896 if ( numDisplayedFromPreviousMonth > 0 ) {
897 firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
898 numDates += numDisplayedFromPreviousMonth; // Previous
899 }
900 numDates += getDaysInMonth(year, month + 1); // Current
901 numDates += (7 - numDates % 7) % 7; // Next
902
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);
907 }
908 for (var j = 0; j < 7; j++) {
909 labels[j] = dateFilter(days[j].date, format.dayHeader);
910 }
911 return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
912 },
913 compare: function(date1, date2) {
914 return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
915 },
916 split: 7,
917 step: { months: 1 }
918 },
919 {
920 name: 'month',
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));
926 }
927 return { objects: months, title: dateFilter(date, format.monthTitle) };
928 },
929 compare: function(date1, date2) {
930 return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
931 },
932 split: 3,
933 step: { years: 1 }
934 },
935 {
936 name: 'year',
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()));
942 }
943 return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
944 },
945 compare: function(date1, date2) {
946 return date1.getFullYear() - date2.getFullYear();
947 },
948 split: 5,
949 step: { years: yearRange }
950 }
951 ];
952
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})));
956 };
957 }])
958
959 .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
960 return {
961 restrict: 'EA',
962 replace: true,
963 templateUrl: 'template/datepicker/datepicker.html',
964 scope: {
965 dateDisabled: '&'
966 },
967 require: ['datepicker', '?^ngModel'],
968 controller: 'DatepickerController',
969 link: function(scope, element, attrs, ctrls) {
970 var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
971
972 if (!ngModel) {
973 return; // do nothing if no ng-model
974 }
975
976 // Configuration parameters
977 var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
978
979 if (attrs.showWeeks) {
980 scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
981 showWeeks = !! value;
982 updateShowWeekNumbers();
983 });
984 } else {
985 updateShowWeekNumbers();
986 }
987
988 if (attrs.min) {
989 scope.$parent.$watch($parse(attrs.min), function(value) {
990 datepickerCtrl.minDate = value ? new Date(value) : null;
991 refill();
992 });
993 }
994 if (attrs.max) {
995 scope.$parent.$watch($parse(attrs.max), function(value) {
996 datepickerCtrl.maxDate = value ? new Date(value) : null;
997 refill();
998 });
999 }
1000
1001 function updateShowWeekNumbers() {
1002 scope.showWeekNumbers = mode === 0 && showWeeks;
1003 }
1004
1005 // Split array into smaller arrays
1006 function split(arr, size) {
1007 var arrays = [];
1008 while (arr.length > 0) {
1009 arrays.push(arr.splice(0, size));
1010 }
1011 return arrays;
1012 }
1013
1014 function refill( updateSelected ) {
1015 var date = null, valid = true;
1016
1017 if ( ngModel.$modelValue ) {
1018 date = new Date( ngModel.$modelValue );
1019
1020 if ( isNaN(date) ) {
1021 valid = false;
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 ) {
1024 selected = date;
1025 }
1026 }
1027 ngModel.$setValidity('date', valid);
1028
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);
1032 });
1033
1034 ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
1035
1036 scope.rows = split(data.objects, currentMode.split);
1037 scope.labels = data.labels || [];
1038 scope.title = data.title;
1039 }
1040
1041 function setMode(value) {
1042 mode = value;
1043 updateShowWeekNumbers();
1044 refill();
1045 }
1046
1047 ngModel.$render = function() {
1048 refill( true );
1049 };
1050
1051 scope.select = function( date ) {
1052 if ( mode === 0 ) {
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 );
1056 refill( true );
1057 } else {
1058 selected = date;
1059 setMode( mode - 1 );
1060 }
1061 };
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) );
1066 refill();
1067 };
1068 scope.toggleMode = function() {
1069 setMode( (mode + 1) % datepickerCtrl.modes.length );
1070 };
1071 scope.getWeekNumber = function(row) {
1072 return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
1073 };
1074
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;
1082 }
1083 }
1084 };
1085 }])
1086
1087 .constant('datepickerPopupConfig', {
1088 dateFormat: 'yyyy-MM-dd',
1089 currentText: 'Today',
1090 toggleWeeksText: 'Weeks',
1091 clearText: 'Clear',
1092 closeText: 'Done',
1093 closeOnDateSelection: true,
1094 appendToBody: false,
1095 showButtonBar: true
1096 })
1097
1098 .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
1099 function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) {
1100 return {
1101 restrict: 'EA',
1102 require: 'ngModel',
1103 link: function(originalScope, element, attrs, ngModel) {
1104 var scope = originalScope.$new(), // create a child scope so we are not polluting original one
1105 dateFormat,
1106 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
1107 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
1108
1109 attrs.$observe('datepickerPopup', function(value) {
1110 dateFormat = value || datepickerPopupConfig.dateFormat;
1111 ngModel.$render();
1112 });
1113
1114 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
1115
1116 originalScope.$on('$destroy', function() {
1117 $popup.remove();
1118 scope.$destroy();
1119 });
1120
1121 attrs.$observe('currentText', function(text) {
1122 scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText;
1123 });
1124 attrs.$observe('toggleWeeksText', function(text) {
1125 scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText;
1126 });
1127 attrs.$observe('clearText', function(text) {
1128 scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText;
1129 });
1130 attrs.$observe('closeText', function(text) {
1131 scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText;
1132 });
1133
1134 var getIsOpen, setIsOpen;
1135 if ( attrs.isOpen ) {
1136 getIsOpen = $parse(attrs.isOpen);
1137 setIsOpen = getIsOpen.assign;
1138
1139 originalScope.$watch(getIsOpen, function updateOpen(value) {
1140 scope.isOpen = !! value;
1141 });
1142 }
1143 scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
1144
1145 function setOpen( value ) {
1146 if (setIsOpen) {
1147 setIsOpen(originalScope, !!value);
1148 } else {
1149 scope.isOpen = !!value;
1150 }
1151 }
1152
1153 var documentClickBind = function(event) {
1154 if (scope.isOpen && event.target !== element[0]) {
1155 scope.$apply(function() {
1156 setOpen(false);
1157 });
1158 }
1159 };
1160
1161 var elementFocusBind = function() {
1162 scope.$apply(function() {
1163 setOpen( true );
1164 });
1165 };
1166
1167 // popup element used to display calendar
1168 var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
1169 popupEl.attr({
1170 'ng-model': 'date',
1171 'ng-change': 'dateSelection()'
1172 });
1173 var datepickerEl = angular.element(popupEl.children()[0]);
1174 if (attrs.datepickerOptions) {
1175 datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
1176 }
1177
1178 // TODO: reverse from dateFilter string to Date object
1179 function parseDate(viewValue) {
1180 if (!viewValue) {
1181 ngModel.$setValidity('date', true);
1182 return null;
1183 } else if (angular.isDate(viewValue)) {
1184 ngModel.$setValidity('date', true);
1185 return viewValue;
1186 } else if (angular.isString(viewValue)) {
1187 var date = new Date(viewValue);
1188 if (isNaN(date)) {
1189 ngModel.$setValidity('date', false);
1190 return undefined;
1191 } else {
1192 ngModel.$setValidity('date', true);
1193 return date;
1194 }
1195 } else {
1196 ngModel.$setValidity('date', false);
1197 return undefined;
1198 }
1199 }
1200 ngModel.$parsers.unshift(parseDate);
1201
1202 // Inner change
1203 scope.dateSelection = function(dt) {
1204 if (angular.isDefined(dt)) {
1205 scope.date = dt;
1206 }
1207 ngModel.$setViewValue(scope.date);
1208 ngModel.$render();
1209
1210 if (closeOnDateSelection) {
1211 setOpen( false );
1212 }
1213 };
1214
1215 element.bind('input change keyup', function() {
1216 scope.$apply(function() {
1217 scope.date = ngModel.$modelValue;
1218 });
1219 });
1220
1221 // Outter change
1222 ngModel.$render = function() {
1223 var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
1224 element.val(date);
1225 scope.date = ngModel.$modelValue;
1226 };
1227
1228 function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1229 if (attribute) {
1230 originalScope.$watch($parse(attribute), function(value){
1231 scope[scopeProperty] = value;
1232 });
1233 datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
1234 }
1235 }
1236 addWatchableAttribute(attrs.min, 'min');
1237 addWatchableAttribute(attrs.max, 'max');
1238 if (attrs.showWeeks) {
1239 addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
1240 } else {
1241 scope.showWeeks = datepickerConfig.showWeeks;
1242 datepickerEl.attr('show-weeks', 'showWeeks');
1243 }
1244 if (attrs.dateDisabled) {
1245 datepickerEl.attr('date-disabled', attrs.dateDisabled);
1246 }
1247
1248 function updatePosition() {
1249 scope.position = appendToBody ? $position.offset(element) : $position.position(element);
1250 scope.position.top = scope.position.top + element.prop('offsetHeight');
1251 }
1252
1253 var documentBindingInitialized = false, elementFocusInitialized = false;
1254 scope.$watch('isOpen', function(value) {
1255 if (value) {
1256 updatePosition();
1257 $document.bind('click', documentClickBind);
1258 if(elementFocusInitialized) {
1259 element.unbind('focus', elementFocusBind);
1260 }
1261 element[0].focus();
1262 documentBindingInitialized = true;
1263 } else {
1264 if(documentBindingInitialized) {
1265 $document.unbind('click', documentClickBind);
1266 }
1267 element.bind('focus', elementFocusBind);
1268 elementFocusInitialized = true;
1269 }
1270
1271 if ( setIsOpen ) {
1272 setIsOpen(originalScope, value);
1273 }
1274 });
1275
1276 scope.today = function() {
1277 scope.dateSelection(new Date());
1278 };
1279 scope.clear = function() {
1280 scope.dateSelection(null);
1281 };
1282
1283 var $popup = $compile(popupEl)(scope);
1284 if ( appendToBody ) {
1285 $document.find('body').append($popup);
1286 } else {
1287 element.after($popup);
1288 }
1289 }
1290 };
1291 }])
1292
1293 .directive('datepickerPopupWrap', function() {
1294 return {
1295 restrict:'EA',
1296 replace: true,
1297 transclude: true,
1298 templateUrl: 'template/datepicker/popup.html',
1299 link:function (scope, element, attrs) {
1300 element.bind('click', function(event) {
1301 event.preventDefault();
1302 event.stopPropagation();
1303 });
1304 }
1305 };
1306 });
1307
1308 /*
1309 * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
1310 * @restrict class or attribute
1311 * @example:
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>
1317 </li>
1318 </ul>
1319 </li>
1320 */
1321
1322 angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
1323 var openElement = null,
1324 closeMenu = angular.noop;
1325 return {
1326 restrict: 'CA',
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) {
1331
1332 var elementWasOpen = (element === openElement);
1333
1334 event.preventDefault();
1335 event.stopPropagation();
1336
1337 if (!!openElement) {
1338 closeMenu();
1339 }
1340
1341 if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
1342 element.parent().addClass('open');
1343 openElement = element;
1344 closeMenu = function (event) {
1345 if (event) {
1346 event.preventDefault();
1347 event.stopPropagation();
1348 }
1349 $document.unbind('click', closeMenu);
1350 element.parent().removeClass('open');
1351 closeMenu = angular.noop;
1352 openElement = null;
1353 };
1354 $document.bind('click', closeMenu);
1355 }
1356 });
1357 }
1358 };
1359 }]);
1360
1361 angular.module('ui.bootstrap.modal', [])
1362
1363 /**
1364 * A helper, internal data structure that acts as a map but also allows getting / removing
1365 * elements in the LIFO order
1366 */
1367 .factory('$$stackedMap', function () {
1368 return {
1369 createNew: function () {
1370 var stack = [];
1371
1372 return {
1373 add: function (key, value) {
1374 stack.push({
1375 key: key,
1376 value: value
1377 });
1378 },
1379 get: function (key) {
1380 for (var i = 0; i < stack.length; i++) {
1381 if (key == stack[i].key) {
1382 return stack[i];
1383 }
1384 }
1385 },
1386 keys: function() {
1387 var keys = [];
1388 for (var i = 0; i < stack.length; i++) {
1389 keys.push(stack[i].key);
1390 }
1391 return keys;
1392 },
1393 top: function () {
1394 return stack[stack.length - 1];
1395 },
1396 remove: function (key) {
1397 var idx = -1;
1398 for (var i = 0; i < stack.length; i++) {
1399 if (key == stack[i].key) {
1400 idx = i;
1401 break;
1402 }
1403 }
1404 return stack.splice(idx, 1)[0];
1405 },
1406 removeTop: function () {
1407 return stack.splice(stack.length - 1, 1)[0];
1408 },
1409 length: function () {
1410 return stack.length;
1411 }
1412 };
1413 }
1414 };
1415 })
1416
1417 /**
1418 * A helper directive for the $modal service. It creates a backdrop element.
1419 */
1420 .directive('modalBackdrop', ['$timeout', function ($timeout) {
1421 return {
1422 restrict: 'EA',
1423 replace: true,
1424 templateUrl: 'template/modal/backdrop.html',
1425 link: function (scope) {
1426
1427 scope.animate = false;
1428
1429 //trigger CSS transitions
1430 $timeout(function () {
1431 scope.animate = true;
1432 });
1433 }
1434 };
1435 }])
1436
1437 .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
1438 return {
1439 restrict: 'EA',
1440 scope: {
1441 index: '@'
1442 },
1443 replace: true,
1444 transclude: true,
1445 templateUrl: 'template/modal/window.html',
1446 link: function (scope, element, attrs) {
1447 scope.windowClass = attrs.windowClass || '';
1448
1449 $timeout(function () {
1450 // trigger CSS transitions
1451 scope.animate = true;
1452 // focus a freshly-opened modal
1453 element[0].focus();
1454 });
1455
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');
1462 }
1463 };
1464 }
1465 };
1466 }])
1467
1468 .factory('$modalStack', ['$document', '$compile', '$rootScope', '$$stackedMap',
1469 function ($document, $compile, $rootScope, $$stackedMap) {
1470
1471 var OPENED_MODAL_CLASS = 'modal-open';
1472
1473 var backdropjqLiteEl, backdropDomEl;
1474 var backdropScope = $rootScope.$new(true);
1475 var openedWindows = $$stackedMap.createNew();
1476 var $modalStack = {};
1477
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;
1484 }
1485 }
1486 return topBackdropIndex;
1487 }
1488
1489 $rootScope.$watch(backdropIndex, function(newBackdropIndex){
1490 backdropScope.index = newBackdropIndex;
1491 });
1492
1493 function removeModalWindow(modalInstance) {
1494
1495 var body = $document.find('body').eq(0);
1496 var modalWindow = openedWindows.get(modalInstance).value;
1497
1498 //clean up the stack
1499 openedWindows.remove(modalInstance);
1500
1501 //remove window DOM element
1502 modalWindow.modalDomEl.remove();
1503 body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
1504
1505 //remove backdrop if no longer needed
1506 if (backdropDomEl && backdropIndex() == -1) {
1507 backdropDomEl.remove();
1508 backdropDomEl = undefined;
1509 }
1510
1511 //destroy scope
1512 modalWindow.modalScope.$destroy();
1513 }
1514
1515 $document.bind('keydown', function (evt) {
1516 var modal;
1517
1518 if (evt.which === 27) {
1519 modal = openedWindows.top();
1520 if (modal && modal.value.keyboard) {
1521 $rootScope.$apply(function () {
1522 $modalStack.dismiss(modal.key);
1523 });
1524 }
1525 }
1526 });
1527
1528 $modalStack.open = function (modalInstance, modal) {
1529
1530 openedWindows.add(modalInstance, {
1531 deferred: modal.deferred,
1532 modalScope: modal.scope,
1533 backdrop: modal.backdrop,
1534 keyboard: modal.keyboard
1535 });
1536
1537 var body = $document.find('body').eq(0);
1538
1539 if (backdropIndex() >= 0 && !backdropDomEl) {
1540 backdropjqLiteEl = angular.element('<div modal-backdrop></div>');
1541 backdropDomEl = $compile(backdropjqLiteEl)(backdropScope);
1542 body.append(backdropDomEl);
1543 }
1544
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);
1549
1550 var modalDomEl = $compile(angularDomEl)(modal.scope);
1551 openedWindows.top().value.modalDomEl = modalDomEl;
1552 body.append(modalDomEl);
1553 body.addClass(OPENED_MODAL_CLASS);
1554 };
1555
1556 $modalStack.close = function (modalInstance, result) {
1557 var modalWindow = openedWindows.get(modalInstance).value;
1558 if (modalWindow) {
1559 modalWindow.deferred.resolve(result);
1560 removeModalWindow(modalInstance);
1561 }
1562 };
1563
1564 $modalStack.dismiss = function (modalInstance, reason) {
1565 var modalWindow = openedWindows.get(modalInstance).value;
1566 if (modalWindow) {
1567 modalWindow.deferred.reject(reason);
1568 removeModalWindow(modalInstance);
1569 }
1570 };
1571
1572 $modalStack.getTop = function () {
1573 return openedWindows.top();
1574 };
1575
1576 return $modalStack;
1577 }])
1578
1579 .provider('$modal', function () {
1580
1581 var $modalProvider = {
1582 options: {
1583 backdrop: true, //can be also false or 'static'
1584 keyboard: true
1585 },
1586 $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
1587 function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
1588
1589 var $modal = {};
1590
1591 function getTemplatePromise(options) {
1592 return options.template ? $q.when(options.template) :
1593 $http.get(options.templateUrl, {cache: $templateCache}).then(function (result) {
1594 return result.data;
1595 });
1596 }
1597
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)));
1603 }
1604 });
1605 return promisesArr;
1606 }
1607
1608 $modal.open = function (modalOptions) {
1609
1610 var modalResultDeferred = $q.defer();
1611 var modalOpenedDeferred = $q.defer();
1612
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);
1619 },
1620 dismiss: function (reason) {
1621 $modalStack.dismiss(modalInstance, reason);
1622 }
1623 };
1624
1625 //merge and clean up options
1626 modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
1627 modalOptions.resolve = modalOptions.resolve || {};
1628
1629 //verify options
1630 if (!modalOptions.template && !modalOptions.templateUrl) {
1631 throw new Error('One of template or templateUrl options is required.');
1632 }
1633
1634 var templateAndResolvePromise =
1635 $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
1636
1637
1638 templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
1639
1640 var modalScope = (modalOptions.scope || $rootScope).$new();
1641 modalScope.$close = modalInstance.close;
1642 modalScope.$dismiss = modalInstance.dismiss;
1643
1644 var ctrlInstance, ctrlLocals = {};
1645 var resolveIter = 1;
1646
1647 //controllers
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++];
1653 });
1654
1655 ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
1656 }
1657
1658 $modalStack.open(modalInstance, {
1659 scope: modalScope,
1660 deferred: modalResultDeferred,
1661 content: tplAndVars[0],
1662 backdrop: modalOptions.backdrop,
1663 keyboard: modalOptions.keyboard,
1664 windowClass: modalOptions.windowClass
1665 });
1666
1667 }, function resolveError(reason) {
1668 modalResultDeferred.reject(reason);
1669 });
1670
1671 templateAndResolvePromise.then(function () {
1672 modalOpenedDeferred.resolve(true);
1673 }, function () {
1674 modalOpenedDeferred.reject(false);
1675 });
1676
1677 return modalInstance;
1678 };
1679
1680 return $modal;
1681 }]
1682 };
1683
1684 return $modalProvider;
1685 });
1686
1687 angular.module('ui.bootstrap.pagination', [])
1688
1689 .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
1690 var self = this,
1691 setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
1692
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();
1698 });
1699 } else {
1700 this.itemsPerPage = defaultItemsPerPage;
1701 }
1702 };
1703
1704 this.noPrevious = function() {
1705 return this.page === 1;
1706 };
1707 this.noNext = function() {
1708 return this.page === $scope.totalPages;
1709 };
1710
1711 this.isActive = function(page) {
1712 return this.page === page;
1713 };
1714
1715 this.calculateTotalPages = function() {
1716 var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
1717 return Math.max(totalPages || 0, 1);
1718 };
1719
1720 this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1721 return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1722 };
1723
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);
1728 }
1729 };
1730
1731 $scope.selectPage = function(page) {
1732 if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
1733 $scope.page = page;
1734 $scope.onSelectPage({ page: page });
1735 }
1736 };
1737
1738 $scope.$watch('page', function() {
1739 self.render();
1740 });
1741
1742 $scope.$watch('totalItems', function() {
1743 $scope.totalPages = self.calculateTotalPages();
1744 });
1745
1746 $scope.$watch('totalPages', function(value) {
1747 setNumPages($scope.$parent, value); // Readonly variable
1748
1749 if ( self.page > value ) {
1750 $scope.selectPage(value);
1751 } else {
1752 self.render();
1753 }
1754 });
1755 }])
1756
1757 .constant('paginationConfig', {
1758 itemsPerPage: 10,
1759 boundaryLinks: false,
1760 directionLinks: true,
1761 firstText: 'First',
1762 previousText: 'Previous',
1763 nextText: 'Next',
1764 lastText: 'Last',
1765 rotate: true
1766 })
1767
1768 .directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
1769 return {
1770 restrict: 'EA',
1771 scope: {
1772 page: '=',
1773 totalItems: '=',
1774 onSelectPage:' &'
1775 },
1776 controller: 'PaginationController',
1777 templateUrl: 'template/pagination/pagination.html',
1778 replace: true,
1779 link: function(scope, element, attrs, paginationCtrl) {
1780
1781 // Setup configuration parameters
1782 var maxSize,
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);
1790
1791 paginationCtrl.init(config.itemsPerPage);
1792
1793 if (attrs.maxSize) {
1794 scope.$parent.$watch($parse(attrs.maxSize), function(value) {
1795 maxSize = parseInt(value, 10);
1796 paginationCtrl.render();
1797 });
1798 }
1799
1800 // Create page object used in template
1801 function makePage(number, text, isActive, isDisabled) {
1802 return {
1803 number: number,
1804 text: text,
1805 active: isActive,
1806 disabled: isDisabled
1807 };
1808 }
1809
1810 paginationCtrl.getPages = function(currentPage, totalPages) {
1811 var pages = [];
1812
1813 // Default page limits
1814 var startPage = 1, endPage = totalPages;
1815 var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
1816
1817 // recompute if maxSize
1818 if ( isMaxSized ) {
1819 if ( rotate ) {
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;
1823
1824 // Adjust if limit is exceeded
1825 if (endPage > totalPages) {
1826 endPage = totalPages;
1827 startPage = endPage - maxSize + 1;
1828 }
1829 } else {
1830 // Visible pages are paginated with maxSize
1831 startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1;
1832
1833 // Adjust last page if limit is exceeded
1834 endPage = Math.min(startPage + maxSize - 1, totalPages);
1835 }
1836 }
1837
1838 // Add page number links
1839 for (var number = startPage; number <= endPage; number++) {
1840 var page = makePage(number, number, paginationCtrl.isActive(number), false);
1841 pages.push(page);
1842 }
1843
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);
1849 }
1850
1851 if ( endPage < totalPages ) {
1852 var nextPageSet = makePage(endPage + 1, '...', false, false);
1853 pages.push(nextPageSet);
1854 }
1855 }
1856
1857 // Add previous & next links
1858 if (directionLinks) {
1859 var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1860 pages.unshift(previousPage);
1861
1862 var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
1863 pages.push(nextPage);
1864 }
1865
1866 // Add first & last links
1867 if (boundaryLinks) {
1868 var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1869 pages.unshift(firstPage);
1870
1871 var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
1872 pages.push(lastPage);
1873 }
1874
1875 return pages;
1876 };
1877 }
1878 };
1879 }])
1880
1881 .constant('pagerConfig', {
1882 itemsPerPage: 10,
1883 previousText: '« Previous',
1884 nextText: 'Next »',
1885 align: true
1886 })
1887
1888 .directive('pager', ['pagerConfig', function(config) {
1889 return {
1890 restrict: 'EA',
1891 scope: {
1892 page: '=',
1893 totalItems: '=',
1894 onSelectPage:' &'
1895 },
1896 controller: 'PaginationController',
1897 templateUrl: 'template/pagination/pager.html',
1898 replace: true,
1899 link: function(scope, element, attrs, paginationCtrl) {
1900
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);
1905
1906 paginationCtrl.init(config.itemsPerPage);
1907
1908 // Create page object used in template
1909 function makePage(number, text, isDisabled, isPrevious, isNext) {
1910 return {
1911 number: number,
1912 text: text,
1913 disabled: isDisabled,
1914 previous: ( align && isPrevious ),
1915 next: ( align && isNext )
1916 };
1917 }
1918
1919 paginationCtrl.getPages = function(currentPage) {
1920 return [
1921 makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
1922 makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
1923 ];
1924 };
1925 }
1926 };
1927 }]);
1928
1929 /**
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.
1933 */
1934 angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
1935
1936 /**
1937 * The $tooltip service creates tooltip- and popover-like directives as well as
1938 * houses global options for them.
1939 */
1940 .provider( '$tooltip', function () {
1941 // The default options tooltip and popover.
1942 var defaultOptions = {
1943 placement: 'top',
1944 animation: true,
1945 popupDelay: 0
1946 };
1947
1948 // Default hide triggers for each show trigger
1949 var triggerMap = {
1950 'mouseenter': 'mouseleave',
1951 'click': 'click',
1952 'focus': 'blur'
1953 };
1954
1955 // The options specified to the provider globally.
1956 var globalOptions = {};
1957
1958 /**
1959 * `options({})` allows global configuration of all tooltips in the
1960 * application.
1961 *
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' } );
1965 * });
1966 */
1967 this.options = function( value ) {
1968 angular.extend( globalOptions, value );
1969 };
1970
1971 /**
1972 * This allows you to extend the set of trigger mappings available. E.g.:
1973 *
1974 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
1975 */
1976 this.setTriggers = function setTriggers ( triggers ) {
1977 angular.extend( triggerMap, triggers );
1978 };
1979
1980 /**
1981 * This is a helper function for translating camel-case to snake-case.
1982 */
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();
1988 });
1989 }
1990
1991 /**
1992 * Returns the actual instance of the $tooltip service.
1993 * TODO support multiple triggers
1994 */
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 );
1998
1999 /**
2000 * Returns an object of show and hide triggers.
2001 *
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.
2006 *
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.
2012 */
2013 function getTriggers ( trigger ) {
2014 var show = trigger || options.trigger || defaultTriggerShow;
2015 var hide = triggerMap[show] || show;
2016 return {
2017 show: show,
2018 hide: hide
2019 };
2020 }
2021
2022 var directiveName = snake_case( type );
2023
2024 var startSym = $interpolate.startSymbol();
2025 var endSym = $interpolate.endSymbol();
2026 var template =
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"'+
2033 '>'+
2034 '</div>';
2035
2036 return {
2037 restrict: 'EA',
2038 scope: true,
2039 link: function link ( scope, element, attrs ) {
2040 var tooltip = $compile( template )( scope );
2041 var transitionTimeout;
2042 var popupTimeout;
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']);
2047
2048 var positionTooltip = function (){
2049 var position,
2050 ttWidth,
2051 ttHeight,
2052 ttPosition;
2053 // Get the position of the directive element.
2054 position = appendToBody ? $position.offset( element ) : $position.position( element );
2055
2056 // Get the height and width of the tooltip so we can center it.
2057 ttWidth = tooltip.prop( 'offsetWidth' );
2058 ttHeight = tooltip.prop( 'offsetHeight' );
2059
2060 // Calculate the tooltip's top and left coordinates to center it with
2061 // this directive.
2062 switch ( scope.tt_placement ) {
2063 case 'right':
2064 ttPosition = {
2065 top: position.top + position.height / 2 - ttHeight / 2,
2066 left: position.left + position.width
2067 };
2068 break;
2069 case 'bottom':
2070 ttPosition = {
2071 top: position.top + position.height,
2072 left: position.left + position.width / 2 - ttWidth / 2
2073 };
2074 break;
2075 case 'left':
2076 ttPosition = {
2077 top: position.top + position.height / 2 - ttHeight / 2,
2078 left: position.left - ttWidth
2079 };
2080 break;
2081 default:
2082 ttPosition = {
2083 top: position.top - ttHeight,
2084 left: position.left + position.width / 2 - ttWidth / 2
2085 };
2086 break;
2087 }
2088
2089 ttPosition.top += 'px';
2090 ttPosition.left += 'px';
2091
2092 // Now set the calculated positioning.
2093 tooltip.css( ttPosition );
2094
2095 };
2096
2097 // By default, the tooltip is not open.
2098 // TODO add ability to start tooltip opened
2099 scope.tt_isOpen = false;
2100
2101 function toggleTooltipBind () {
2102 if ( ! scope.tt_isOpen ) {
2103 showTooltipBind();
2104 } else {
2105 hideTooltipBind();
2106 }
2107 }
2108
2109 // Show the tooltip with delay if specified, otherwise show it immediately
2110 function showTooltipBind() {
2111 if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
2112 return;
2113 }
2114 if ( scope.tt_popupDelay ) {
2115 popupTimeout = $timeout( show, scope.tt_popupDelay );
2116 popupTimeout.then(function(reposition){reposition();});
2117 } else {
2118 scope.$apply( show )();
2119 }
2120 }
2121
2122 function hideTooltipBind () {
2123 scope.$apply(function () {
2124 hide();
2125 });
2126 }
2127
2128 // Show the tooltip popup element.
2129 function show() {
2130
2131
2132 // Don't show empty tooltips.
2133 if ( ! scope.tt_content ) {
2134 return angular.noop;
2135 }
2136
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 );
2141 }
2142
2143 // Set the initial positioning.
2144 tooltip.css({ top: 0, left: 0, display: 'block' });
2145
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 );
2150 } else {
2151 element.after( tooltip );
2152 }
2153
2154 positionTooltip();
2155
2156 // And show the tooltip.
2157 scope.tt_isOpen = true;
2158
2159 // Return positioning function as promise callback for correct
2160 // positioning after draw.
2161 return positionTooltip;
2162 }
2163
2164 // Hide the tooltip popup element.
2165 function hide() {
2166 // First things first: we don't show it anymore.
2167 scope.tt_isOpen = false;
2168
2169 //if tooltip is going to be shown after delay, we must cancel this
2170 $timeout.cancel( popupTimeout );
2171
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 () {
2177 tooltip.remove();
2178 }, 500);
2179 } else {
2180 tooltip.remove();
2181 }
2182 }
2183
2184 /**
2185 * Observe the relevant attributes.
2186 */
2187 attrs.$observe( type, function ( val ) {
2188 scope.tt_content = val;
2189
2190 if (!val && scope.tt_isOpen ) {
2191 hide();
2192 }
2193 });
2194
2195 attrs.$observe( prefix+'Title', function ( val ) {
2196 scope.tt_title = val;
2197 });
2198
2199 attrs.$observe( prefix+'Placement', function ( val ) {
2200 scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
2201 });
2202
2203 attrs.$observe( prefix+'PopupDelay', function ( val ) {
2204 var delay = parseInt( val, 10 );
2205 scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
2206 });
2207
2208 var unregisterTriggers = function() {
2209 if (hasRegisteredTriggers) {
2210 element.unbind( triggers.show, showTooltipBind );
2211 element.unbind( triggers.hide, hideTooltipBind );
2212 }
2213 };
2214
2215 attrs.$observe( prefix+'Trigger', function ( val ) {
2216 unregisterTriggers();
2217
2218 triggers = getTriggers( val );
2219
2220 if ( triggers.show === triggers.hide ) {
2221 element.bind( triggers.show, toggleTooltipBind );
2222 } else {
2223 element.bind( triggers.show, showTooltipBind );
2224 element.bind( triggers.hide, hideTooltipBind );
2225 }
2226
2227 hasRegisteredTriggers = true;
2228 });
2229
2230 var animation = scope.$eval(attrs[prefix + 'Animation']);
2231 scope.tt_animation = angular.isDefined(animation) ? !!animation : options.animation;
2232
2233 attrs.$observe( prefix+'AppendToBody', function ( val ) {
2234 appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
2235 });
2236
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
2239 // by the change.
2240 if ( appendToBody ) {
2241 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
2242 if ( scope.tt_isOpen ) {
2243 hide();
2244 }
2245 });
2246 }
2247
2248 // Make sure tooltip is destroyed and removed.
2249 scope.$on('$destroy', function onDestroyTooltip() {
2250 $timeout.cancel( transitionTimeout );
2251 $timeout.cancel( popupTimeout );
2252 unregisterTriggers();
2253 tooltip.remove();
2254 tooltip.unbind();
2255 tooltip = null;
2256 });
2257 }
2258 };
2259 };
2260 }];
2261 })
2262
2263 .directive( 'tooltipPopup', function () {
2264 return {
2265 restrict: 'EA',
2266 replace: true,
2267 scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
2268 templateUrl: 'template/tooltip/tooltip-popup.html'
2269 };
2270 })
2271
2272 .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
2273 return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
2274 }])
2275
2276 .directive( 'tooltipHtmlUnsafePopup', function () {
2277 return {
2278 restrict: 'EA',
2279 replace: true,
2280 scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
2281 templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
2282 };
2283 })
2284
2285 .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
2286 return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
2287 }]);
2288
2289 /**
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.
2293 */
2294 angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
2295 .directive( 'popoverPopup', function () {
2296 return {
2297 restrict: 'EA',
2298 replace: true,
2299 scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
2300 templateUrl: 'template/popover/popover.html'
2301 };
2302 })
2303 .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
2304 return $tooltip( 'popover', 'popover', 'click' );
2305 }]);
2306
2307
2308 angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2309
2310 .constant('progressConfig', {
2311 animate: true,
2312 max: 100
2313 })
2314
2315 .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
2316 var self = this,
2317 bars = [],
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;
2320
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;
2325 }
2326 bars.push(bar);
2327
2328 this.update(element, bar.value, oldValue);
2329
2330 bar.$watch('value', function(value, oldValue) {
2331 if (value !== oldValue) {
2332 self.update(element, value, oldValue);
2333 }
2334 });
2335
2336 bar.$on('$destroy', function() {
2337 self.removeBar(bar);
2338 });
2339 };
2340
2341 // Update bar element width
2342 this.update = function(element, newValue, oldValue) {
2343 var percent = this.getPercentage(newValue);
2344
2345 if (animate) {
2346 element.css('width', this.getPercentage(oldValue) + '%');
2347 $transition(element, {width: percent + '%'});
2348 } else {
2349 element.css({'transition': 'none', 'width': percent + '%'});
2350 }
2351 };
2352
2353 this.removeBar = function(bar) {
2354 bars.splice(bars.indexOf(bar), 1);
2355 };
2356
2357 this.getPercentage = function(value) {
2358 return Math.round(100 * value / max);
2359 };
2360 }])
2361
2362 .directive('progress', function() {
2363 return {
2364 restrict: 'EA',
2365 replace: true,
2366 transclude: true,
2367 controller: 'ProgressController',
2368 require: 'progress',
2369 scope: {},
2370 template: '<div class="progress" ng-transclude></div>'
2371 //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
2372 };
2373 })
2374
2375 .directive('bar', function() {
2376 return {
2377 restrict: 'EA',
2378 replace: true,
2379 transclude: true,
2380 require: '^progress',
2381 scope: {
2382 value: '=',
2383 type: '@'
2384 },
2385 templateUrl: 'template/progressbar/bar.html',
2386 link: function(scope, element, attrs, progressCtrl) {
2387 progressCtrl.addBar(scope, element);
2388 }
2389 };
2390 })
2391
2392 .directive('progressbar', function() {
2393 return {
2394 restrict: 'EA',
2395 replace: true,
2396 transclude: true,
2397 controller: 'ProgressController',
2398 scope: {
2399 value: '=',
2400 type: '@'
2401 },
2402 templateUrl: 'template/progressbar/progressbar.html',
2403 link: function(scope, element, attrs, progressCtrl) {
2404 progressCtrl.addBar(scope, angular.element(element.children()[0]));
2405 }
2406 };
2407 });
2408 angular.module('ui.bootstrap.rating', [])
2409
2410 .constant('ratingConfig', {
2411 max: 5,
2412 stateOn: null,
2413 stateOff: null
2414 })
2415
2416 .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
2417
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;
2421
2422 this.createRateObjects = function(states) {
2423 var defaultOptions = {
2424 stateOn: this.stateOn,
2425 stateOff: this.stateOff
2426 };
2427
2428 for (var i = 0, n = states.length; i < n; i++) {
2429 states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
2430 }
2431 return states;
2432 };
2433
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));
2436
2437 $scope.rate = function(value) {
2438 if ( $scope.readonly || $scope.value === value) {
2439 return;
2440 }
2441
2442 $scope.value = value;
2443 };
2444
2445 $scope.enter = function(value) {
2446 if ( ! $scope.readonly ) {
2447 $scope.val = value;
2448 }
2449 $scope.onHover({value: value});
2450 };
2451
2452 $scope.reset = function() {
2453 $scope.val = angular.copy($scope.value);
2454 $scope.onLeave();
2455 };
2456
2457 $scope.$watch('value', function(value) {
2458 $scope.val = value;
2459 });
2460
2461 $scope.readonly = false;
2462 if ($attrs.readonly) {
2463 $scope.$parent.$watch($parse($attrs.readonly), function(value) {
2464 $scope.readonly = !!value;
2465 });
2466 }
2467 }])
2468
2469 .directive('rating', function() {
2470 return {
2471 restrict: 'EA',
2472 scope: {
2473 value: '=',
2474 onHover: '&',
2475 onLeave: '&'
2476 },
2477 controller: 'RatingController',
2478 templateUrl: 'template/rating/rating.html',
2479 replace: true
2480 };
2481 });
2482
2483 /**
2484 * @ngdoc overview
2485 * @name ui.bootstrap.tabs
2486 *
2487 * @description
2488 * AngularJS version of the tabs directive.
2489 */
2490
2491 angular.module('ui.bootstrap.tabs', [])
2492
2493 .controller('TabsetController', ['$scope', function TabsetCtrl($scope) {
2494 var ctrl = this,
2495 tabs = ctrl.tabs = $scope.tabs = [];
2496
2497 ctrl.select = function(tab) {
2498 angular.forEach(tabs, function(tab) {
2499 tab.active = false;
2500 });
2501 tab.active = true;
2502 };
2503
2504 ctrl.addTab = function addTab(tab) {
2505 tabs.push(tab);
2506 if (tabs.length === 1 || tab.active) {
2507 ctrl.select(tab);
2508 }
2509 };
2510
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]);
2518 }
2519 tabs.splice(index, 1);
2520 };
2521 }])
2522
2523 /**
2524 * @ngdoc directive
2525 * @name ui.bootstrap.tabs.directive:tabset
2526 * @restrict EA
2527 *
2528 * @description
2529 * Tabset is the outer container for the tabs directive
2530 *
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.
2533 *
2534 * @example
2535 <example module="ui.bootstrap">
2536 <file name="index.html">
2537 <tabset>
2538 <tab heading="Tab 1"><b>First</b> Content!</tab>
2539 <tab heading="Tab 2"><i>Second</i> Content!</tab>
2540 </tabset>
2541 <hr />
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>
2545 </tabset>
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>
2549 </tabset>
2550 </file>
2551 </example>
2552 */
2553 .directive('tabset', function() {
2554 return {
2555 restrict: 'EA',
2556 transclude: true,
2557 replace: true,
2558 scope: {},
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';
2565 }
2566 };
2567 })
2568
2569 /**
2570 * @ngdoc directive
2571 * @name ui.bootstrap.tabs.directive:tab
2572 * @restrict EA
2573 *
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.
2578 *
2579 * @description
2580 * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
2581 *
2582 * @example
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
2588 </button>
2589 <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
2590 Enable/disable item 2, using disabled binding
2591 </button>
2592 <br />
2593 <tabset>
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!
2598 </tab>
2599 <tab ng-repeat="item in items"
2600 heading="{{item.title}}"
2601 disabled="item.disabled"
2602 active="item.active">
2603 {{item.content}}
2604 </tab>
2605 </tabset>
2606 </div>
2607 </file>
2608 <file name="script.js">
2609 function TabsDemoCtrl($scope) {
2610 $scope.items = [
2611 { title:"Dynamic Title 1", content:"Dynamic Item 0" },
2612 { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
2613 ];
2614
2615 $scope.alertMe = function() {
2616 setTimeout(function() {
2617 alert("You've selected the alert tab!");
2618 });
2619 };
2620 };
2621 </file>
2622 </example>
2623 */
2624
2625 /**
2626 * @ngdoc directive
2627 * @name ui.bootstrap.tabs.directive:tabHeading
2628 * @restrict EA
2629 *
2630 * @description
2631 * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
2632 *
2633 * @example
2634 <example module="ui.bootstrap">
2635 <file name="index.html">
2636 <tabset>
2637 <tab>
2638 <tab-heading><b>HTML</b> in my titles?!</tab-heading>
2639 And some content, too!
2640 </tab>
2641 <tab>
2642 <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
2643 That's right.
2644 </tab>
2645 </tabset>
2646 </file>
2647 </example>
2648 */
2649 .directive('tab', ['$parse', function($parse) {
2650 return {
2651 require: '^tabset',
2652 restrict: 'EA',
2653 replace: true,
2654 templateUrl: 'template/tabs/tab.html',
2655 transclude: true,
2656 scope: {
2657 heading: '@',
2658 onSelect: '&select', //This callback is called in contentHeadingTransclude
2659 //once it inserts the tab's content into the dom
2660 onDeselect: '&deselect'
2661 },
2662 controller: function() {
2663 //Empty controller so other directives can require being 'under' a tab
2664 },
2665 compile: function(elm, attrs, transclude) {
2666 return function postLink(scope, elm, attrs, tabsetCtrl) {
2667 var getActive, setActive;
2668 if (attrs.active) {
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 ===
2674 // oldVal)
2675 if (value !== oldVal) {
2676 scope.active = !!value;
2677 }
2678 });
2679 scope.active = getActive(scope.$parent);
2680 } else {
2681 setActive = getActive = angular.noop;
2682 }
2683
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);
2688 if (active) {
2689 tabsetCtrl.select(scope);
2690 scope.onSelect();
2691 } else {
2692 scope.onDeselect();
2693 }
2694 });
2695
2696 scope.disabled = false;
2697 if ( attrs.disabled ) {
2698 scope.$parent.$watch($parse(attrs.disabled), function(value) {
2699 scope.disabled = !! value;
2700 });
2701 }
2702
2703 scope.select = function() {
2704 if ( ! scope.disabled ) {
2705 scope.active = true;
2706 }
2707 };
2708
2709 tabsetCtrl.addTab(scope);
2710 scope.$on('$destroy', function() {
2711 tabsetCtrl.removeTab(scope);
2712 });
2713
2714
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;
2718 };
2719 }
2720 };
2721 }])
2722
2723 .directive('tabHeadingTransclude', [function() {
2724 return {
2725 restrict: 'A',
2726 require: '^tab',
2727 link: function(scope, elm, attrs, tabCtrl) {
2728 scope.$watch('headingElement', function updateHeadingElement(heading) {
2729 if (heading) {
2730 elm.html('');
2731 elm.append(heading);
2732 }
2733 });
2734 }
2735 };
2736 }])
2737
2738 .directive('tabContentTransclude', function() {
2739 return {
2740 restrict: 'A',
2741 require: '^tabset',
2742 link: function(scope, elm, attrs) {
2743 var tab = scope.$eval(attrs.tabContentTransclude);
2744
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;
2752 } else {
2753 elm.append(node);
2754 }
2755 });
2756 });
2757 }
2758 };
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'
2765 );
2766 }
2767 })
2768
2769 ;
2770
2771 angular.module('ui.bootstrap.timepicker', [])
2772
2773 .constant('timepickerConfig', {
2774 hourStep: 1,
2775 minuteStep: 1,
2776 showMeridian: true,
2777 meridians: null,
2778 readonlyInput: false,
2779 mousewheel: true
2780 })
2781
2782 .directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) {
2783 return {
2784 restrict: 'EA',
2785 require:'?^ngModel',
2786 replace: true,
2787 scope: {},
2788 templateUrl: 'template/timepicker/timepicker.html',
2789 link: function(scope, element, attrs, ngModel) {
2790 if ( !ngModel ) {
2791 return; // do nothing if no ng-model
2792 }
2793
2794 var selected = new Date(),
2795 meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
2796
2797 var hourStep = timepickerConfig.hourStep;
2798 if (attrs.hourStep) {
2799 scope.$parent.$watch($parse(attrs.hourStep), function(value) {
2800 hourStep = parseInt(value, 10);
2801 });
2802 }
2803
2804 var minuteStep = timepickerConfig.minuteStep;
2805 if (attrs.minuteStep) {
2806 scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
2807 minuteStep = parseInt(value, 10);
2808 });
2809 }
2810
2811 // 12H / 24H mode
2812 scope.showMeridian = timepickerConfig.showMeridian;
2813 if (attrs.showMeridian) {
2814 scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
2815 scope.showMeridian = !!value;
2816
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 );
2822 refresh();
2823 }
2824 } else {
2825 updateTemplate();
2826 }
2827 });
2828 }
2829
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);
2834 if ( !valid ) {
2835 return undefined;
2836 }
2837
2838 if ( scope.showMeridian ) {
2839 if ( hours === 12 ) {
2840 hours = 0;
2841 }
2842 if ( scope.meridian === meridians[1] ) {
2843 hours = hours + 12;
2844 }
2845 }
2846 return hours;
2847 }
2848
2849 function getMinutesFromTemplate() {
2850 var minutes = parseInt(scope.minutes, 10);
2851 return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
2852 }
2853
2854 function pad( value ) {
2855 return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
2856 }
2857
2858 // Input elements
2859 var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
2860
2861 // Respond on mousewheel spin
2862 var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2863 if ( mousewheel ) {
2864
2865 var isScrollingUp = function(e) {
2866 if (e.originalEvent) {
2867 e = e.originalEvent;
2868 }
2869 //pick correct delta variable depending on event
2870 var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2871 return (e.detail || delta > 0);
2872 };
2873
2874 hoursInputEl.bind('mousewheel wheel', function(e) {
2875 scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2876 e.preventDefault();
2877 });
2878
2879 minutesInputEl.bind('mousewheel wheel', function(e) {
2880 scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
2881 e.preventDefault();
2882 });
2883 }
2884
2885 scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
2886 if ( ! scope.readonlyInput ) {
2887
2888 var invalidate = function(invalidHours, invalidMinutes) {
2889 ngModel.$setViewValue( null );
2890 ngModel.$setValidity('time', false);
2891 if (angular.isDefined(invalidHours)) {
2892 scope.invalidHours = invalidHours;
2893 }
2894 if (angular.isDefined(invalidMinutes)) {
2895 scope.invalidMinutes = invalidMinutes;
2896 }
2897 };
2898
2899 scope.updateHours = function() {
2900 var hours = getHoursFromTemplate();
2901
2902 if ( angular.isDefined(hours) ) {
2903 selected.setHours( hours );
2904 refresh( 'h' );
2905 } else {
2906 invalidate(true);
2907 }
2908 };
2909
2910 hoursInputEl.bind('blur', function(e) {
2911 if ( !scope.validHours && scope.hours < 10) {
2912 scope.$apply( function() {
2913 scope.hours = pad( scope.hours );
2914 });
2915 }
2916 });
2917
2918 scope.updateMinutes = function() {
2919 var minutes = getMinutesFromTemplate();
2920
2921 if ( angular.isDefined(minutes) ) {
2922 selected.setMinutes( minutes );
2923 refresh( 'm' );
2924 } else {
2925 invalidate(undefined, true);
2926 }
2927 };
2928
2929 minutesInputEl.bind('blur', function(e) {
2930 if ( !scope.invalidMinutes && scope.minutes < 10 ) {
2931 scope.$apply( function() {
2932 scope.minutes = pad( scope.minutes );
2933 });
2934 }
2935 });
2936 } else {
2937 scope.updateHours = angular.noop;
2938 scope.updateMinutes = angular.noop;
2939 }
2940
2941 ngModel.$render = function() {
2942 var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
2943
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.');
2947 } else {
2948 if ( date ) {
2949 selected = date;
2950 }
2951 makeValid();
2952 updateTemplate();
2953 }
2954 };
2955
2956 // Call internally when we know that model is valid.
2957 function refresh( keyboardChange ) {
2958 makeValid();
2959 ngModel.$setViewValue( new Date(selected) );
2960 updateTemplate( keyboardChange );
2961 }
2962
2963 function makeValid() {
2964 ngModel.$setValidity('time', true);
2965 scope.invalidHours = false;
2966 scope.invalidMinutes = false;
2967 }
2968
2969 function updateTemplate( keyboardChange ) {
2970 var hours = selected.getHours(), minutes = selected.getMinutes();
2971
2972 if ( scope.showMeridian ) {
2973 hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
2974 }
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];
2978 }
2979
2980 function addMinutes( minutes ) {
2981 var dt = new Date( selected.getTime() + minutes * 60000 );
2982 selected.setHours( dt.getHours(), dt.getMinutes() );
2983 refresh();
2984 }
2985
2986 scope.incrementHours = function() {
2987 addMinutes( hourStep * 60 );
2988 };
2989 scope.decrementHours = function() {
2990 addMinutes( - hourStep * 60 );
2991 };
2992 scope.incrementMinutes = function() {
2993 addMinutes( minuteStep );
2994 };
2995 scope.decrementMinutes = function() {
2996 addMinutes( - minuteStep );
2997 };
2998 scope.toggleMeridian = function() {
2999 addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
3000 };
3001 }
3002 };
3003 }]);
3004
3005 angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3006
3007 /**
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
3010 */
3011 .factory('typeaheadParser', ['$parse', function ($parse) {
3012
3013 // 00000111000000000000022200000000000000003333333333333330000000000044000
3014 var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
3015
3016 return {
3017 parse:function (input) {
3018
3019 var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
3020 if (!match) {
3021 throw new Error(
3022 "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
3023 " but got '" + input + "'.");
3024 }
3025
3026 return {
3027 itemName:match[3],
3028 source:$parse(match[4]),
3029 viewMapper:$parse(match[2] || match[1]),
3030 modelMapper:$parse(match[1])
3031 };
3032 }
3033 };
3034 }])
3035
3036 .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser',
3037 function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
3038
3039 var HOT_KEYS = [9, 13, 27, 38, 40];
3040
3041 return {
3042 require:'ngModel',
3043 link:function (originalScope, element, attrs, modelCtrl) {
3044
3045 //SUPPORTED ATTRIBUTES (OPTIONS)
3046
3047 //minimal no of characters that needs to be entered before typeahead kicks-in
3048 var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
3049
3050 //minimal wait time after last character typed before typehead kicks-in
3051 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
3052
3053 //should it restrict model values to the ones selected from the popup only?
3054 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
3055
3056 //binding to a variable that indicates if matches are being retrieved asynchronously
3057 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
3058
3059 //a callback executed when a match is selected
3060 var onSelectCallback = $parse(attrs.typeaheadOnSelect);
3061
3062 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
3063
3064 var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
3065
3066 //INTERNAL VARIABLES
3067
3068 //model setter executed upon match selection
3069 var $setModelValue = $parse(attrs.ngModel).assign;
3070
3071 //expressions used by typeahead
3072 var parserResult = typeaheadParser.parse(attrs.typeahead);
3073
3074 var hasFocus;
3075
3076 //pop-up element used to display matches
3077 var popUpEl = angular.element('<div typeahead-popup></div>');
3078 popUpEl.attr({
3079 matches: 'matches',
3080 active: 'activeIdx',
3081 select: 'select(activeIdx)',
3082 query: 'query',
3083 position: 'position'
3084 });
3085 //custom item template
3086 if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
3087 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
3088 }
3089
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(){
3094 scope.$destroy();
3095 });
3096
3097 var resetMatches = function() {
3098 scope.matches = [];
3099 scope.activeIdx = -1;
3100 };
3101
3102 var getMatchesAsync = function(inputValue) {
3103
3104 var locals = {$viewValue: inputValue};
3105 isLoadingSetter(originalScope, true);
3106 $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
3107
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) {
3112
3113 scope.activeIdx = 0;
3114 scope.matches.length = 0;
3115
3116 //transform labels
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),
3121 model: matches[i]
3122 });
3123 }
3124
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');
3131
3132 } else {
3133 resetMatches();
3134 }
3135 isLoadingSetter(originalScope, false);
3136 }
3137 }, function(){
3138 resetMatches();
3139 isLoadingSetter(originalScope, false);
3140 });
3141 };
3142
3143 resetMatches();
3144
3145 //we need to propagate user's query so we can higlight matches
3146 scope.query = undefined;
3147
3148 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3149 var timeoutPromise;
3150
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) {
3154
3155 hasFocus = true;
3156
3157 if (inputValue && inputValue.length >= minSearch) {
3158 if (waitTime > 0) {
3159 if (timeoutPromise) {
3160 $timeout.cancel(timeoutPromise);//cancel previous timeout
3161 }
3162 timeoutPromise = $timeout(function () {
3163 getMatchesAsync(inputValue);
3164 }, waitTime);
3165 } else {
3166 getMatchesAsync(inputValue);
3167 }
3168 } else {
3169 isLoadingSetter(originalScope, false);
3170 resetMatches();
3171 }
3172
3173 if (isEditable) {
3174 return inputValue;
3175 } else {
3176 if (!inputValue) {
3177 // Reset in case user had typed something previously.
3178 modelCtrl.$setValidity('editable', true);
3179 return inputValue;
3180 } else {
3181 modelCtrl.$setValidity('editable', false);
3182 return undefined;
3183 }
3184 }
3185 });
3186
3187 modelCtrl.$formatters.push(function (modelValue) {
3188
3189 var candidateViewValue, emptyViewValue;
3190 var locals = {};
3191
3192 if (inputFormatter) {
3193
3194 locals['$model'] = modelValue;
3195 return inputFormatter(originalScope, locals);
3196
3197 } else {
3198
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);
3205
3206 return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3207 }
3208 });
3209
3210 scope.select = function (activeIdx) {
3211 //called from within the $digest() cycle
3212 var locals = {};
3213 var model, item;
3214
3215 locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3216 model = parserResult.modelMapper(originalScope, locals);
3217 $setModelValue(originalScope, model);
3218 modelCtrl.$setValidity('editable', true);
3219
3220 onSelectCallback(originalScope, {
3221 $item: item,
3222 $model: model,
3223 $label: parserResult.viewMapper(originalScope, locals)
3224 });
3225
3226 resetMatches();
3227
3228 //return focus to the input element if a mach was selected via a mouse click event
3229 element[0].focus();
3230 };
3231
3232 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
3233 element.bind('keydown', function (evt) {
3234
3235 //typeahead is open and an "interesting" key was pressed
3236 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
3237 return;
3238 }
3239
3240 evt.preventDefault();
3241
3242 if (evt.which === 40) {
3243 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
3244 scope.$digest();
3245
3246 } else if (evt.which === 38) {
3247 scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
3248 scope.$digest();
3249
3250 } else if (evt.which === 13 || evt.which === 9) {
3251 scope.$apply(function () {
3252 scope.select(scope.activeIdx);
3253 });
3254
3255 } else if (evt.which === 27) {
3256 evt.stopPropagation();
3257
3258 resetMatches();
3259 scope.$digest();
3260 }
3261 });
3262
3263 element.bind('blur', function (evt) {
3264 hasFocus = false;
3265 });
3266
3267 // Keep reference to click handler to unbind it.
3268 var dismissClickHandler = function (evt) {
3269 if (element[0] !== evt.target) {
3270 resetMatches();
3271 scope.$digest();
3272 }
3273 };
3274
3275 $document.bind('click', dismissClickHandler);
3276
3277 originalScope.$on('$destroy', function(){
3278 $document.unbind('click', dismissClickHandler);
3279 });
3280
3281 var $popup = $compile(popUpEl)(scope);
3282 if ( appendToBody ) {
3283 $document.find('body').append($popup);
3284 } else {
3285 element.after($popup);
3286 }
3287 }
3288 };
3289
3290 }])
3291
3292 .directive('typeaheadPopup', function () {
3293 return {
3294 restrict:'EA',
3295 scope:{
3296 matches:'=',
3297 query:'=',
3298 active:'=',
3299 position:'=',
3300 select:'&'
3301 },
3302 replace:true,
3303 templateUrl:'template/typeahead/typeahead-popup.html',
3304 link:function (scope, element, attrs) {
3305
3306 scope.templateUrl = attrs.templateUrl;
3307
3308 scope.isOpen = function () {
3309 return scope.matches.length > 0;
3310 };
3311
3312 scope.isActive = function (matchIdx) {
3313 return scope.active == matchIdx;
3314 };
3315
3316 scope.selectActive = function (matchIdx) {
3317 scope.active = matchIdx;
3318 };
3319
3320 scope.selectMatch = function (activeIdx) {
3321 scope.select({activeIdx:activeIdx});
3322 };
3323 }
3324 };
3325 })
3326
3327 .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
3328 return {
3329 restrict:'EA',
3330 scope:{
3331 index:'=',
3332 match:'=',
3333 query:'='
3334 },
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));
3339 });
3340 }
3341 };
3342 }])
3343
3344 .filter('typeaheadHighlight', function() {
3345
3346 function escapeRegexp(queryToEscape) {
3347 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
3348 }
3349
3350 return function(matchItem, query) {
3351 return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3352 };
3353 });
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" +
3360 " </h4>\n" +
3361 " </div>\n" +
3362 " <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" +
3363 " <div class=\"panel-body\" ng-transclude></div>\n" +
3364 " </div>\n" +
3365 "</div>");
3366 }]);
3367
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>");
3371 }]);
3372
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()'>&times;</button>\n" +
3377 " <div ng-transclude></div>\n" +
3378 "</div>\n" +
3379 "");
3380 }]);
3381
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" +
3387 " </ol>\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" +
3391 "</div>\n" +
3392 "");
3393 }]);
3394
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" +
3404 "");
3405 }]);
3406
3407 angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
3408 $templateCache.put("template/datepicker/datepicker.html",
3409 "<table>\n" +
3410 " <thead>\n" +
3411 " <tr>\n" +
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" +
3415 " </tr>\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" +
3419 " </tr>\n" +
3420 " </thead>\n" +
3421 " <tbody>\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" +
3426 " </td>\n" +
3427 " </tr>\n" +
3428 " </tbody>\n" +
3429 "</table>\n" +
3430 "");
3431 }]);
3432
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" +
3442 " </span>\n" +
3443 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"isOpen = false\">{{closeText}}</button>\n" +
3444 " </li>\n" +
3445 "</ul>\n" +
3446 "");
3447 }]);
3448
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>");
3452 }]);
3453
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" +
3458 "</div>");
3459 }]);
3460
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" +
3465 "</ul>");
3466 }]);
3467
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" +
3472 "</ul>");
3473 }]);
3474
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" +
3480 "</div>\n" +
3481 "");
3482 }]);
3483
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" +
3489 "</div>\n" +
3490 "");
3491 }]);
3492
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" +
3497 "\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" +
3501 " </div>\n" +
3502 "</div>\n" +
3503 "");
3504 }]);
3505
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>");
3509 }]);
3510
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>");
3514 }]);
3515
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>");
3519 }]);
3520
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" +
3525 "</span>");
3526 }]);
3527
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" +
3532 "</li>\n" +
3533 "");
3534 }]);
3535
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" +
3539 "</ul>\n" +
3540 "");
3541 }]);
3542
3543 angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
3544 $templateCache.put("template/tabs/tabset.html",
3545 "\n" +
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" +
3553 " </div>\n" +
3554 " </div>\n" +
3555 "</div>\n" +
3556 "");
3557 }]);
3558
3559 angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) {
3560 $templateCache.put("template/timepicker/timepicker.html",
3561 "<table>\n" +
3562 " <tbody>\n" +
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>&nbsp;</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" +
3568 " </tr>\n" +
3569 " <tr>\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" +
3572 " </td>\n" +
3573 " <td>:</td>\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" +
3576 " </td>\n" +
3577 " <td ng-show=\"showMeridian\"><button class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" +
3578 " </tr>\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>&nbsp;</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" +
3584 " </tr>\n" +
3585 " </tbody>\n" +
3586 "</table>\n" +
3587 "");
3588 }]);
3589
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>");
3593 }]);
3594
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" +
3600 " </li>\n" +
3601 "</ul>");
3602 }]);