1/* 2 * angular-ui-bootstrap 3 * http://angular-ui.github.io/bootstrap/ 4 5 * Version: 0.13.0 - 2015-05-02 6 * License: MIT 7 */ 8angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","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.transition","ui.bootstrap.typeahead"]); 9angular.module("ui.bootstrap.tpls", ["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/carousel/carousel.html","template/carousel/slide.html","template/datepicker/datepicker.html","template/datepicker/day.html","template/datepicker/month.html","template/datepicker/popup.html","template/datepicker/year.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-popup.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/tooltip/tooltip-template-popup.html","template/popover/popover-template.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/timepicker/timepicker.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]); 10angular.module('ui.bootstrap.collapse', []) 11 12 .directive('collapse', ['$animate', function ($animate) { 13 14 return { 15 link: function (scope, element, attrs) { 16 function expand() { 17 element.removeClass('collapse').addClass('collapsing'); 18 $animate.addClass(element, 'in', { 19 to: { height: element[0].scrollHeight + 'px' } 20 }).then(expandDone); 21 } 22 23 function expandDone() { 24 element.removeClass('collapsing'); 25 element.css({height: 'auto'}); 26 } 27 28 function collapse() { 29 element 30 // IMPORTANT: The height must be set before adding "collapsing" class. 31 // Otherwise, the browser attempts to animate from height 0 (in 32 // collapsing class) to the given height here. 33 .css({height: element[0].scrollHeight + 'px'}) 34 // initially all panel collapse have the collapse class, this removal 35 // prevents the animation from jumping to collapsed state 36 .removeClass('collapse') 37 .addClass('collapsing'); 38 39 $animate.removeClass(element, 'in', { 40 to: {height: '0'} 41 }).then(collapseDone); 42 } 43 44 function collapseDone() { 45 element.css({height: '0'}); // Required so that collapse works when animation is disabled 46 element.removeClass('collapsing'); 47 element.addClass('collapse'); 48 } 49 50 scope.$watch(attrs.collapse, function (shouldCollapse) { 51 if (shouldCollapse) { 52 collapse(); 53 } else { 54 expand(); 55 } 56 }); 57 } 58 }; 59 }]); 60 61angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) 62 63.constant('accordionConfig', { 64 closeOthers: true 65}) 66 67.controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) { 68 69 // This array keeps track of the accordion groups 70 this.groups = []; 71 72 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to 73 this.closeOthers = function(openGroup) { 74 var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; 75 if ( closeOthers ) { 76 angular.forEach(this.groups, function (group) { 77 if ( group !== openGroup ) { 78 group.isOpen = false;
79 } 80 }); 81 } 82 }; 83 84 // This is called from the accordion-group directive to add itself to the accordion 85 this.addGroup = function(groupScope) { 86 var that = this; 87 this.groups.push(groupScope); 88 89 groupScope.$on('$destroy', function (event) { 90 that.removeGroup(groupScope); 91 }); 92 }; 93 94 // This is called from the accordion-group directive when to remove itself 95 this.removeGroup = function(group) { 96 var index = this.groups.indexOf(group); 97 if ( index !== -1 ) { 98 this.groups.splice(index, 1); 99 } 100 }; 101 102}]) 103 104// The accordion directive simply sets up the directive controller 105// and adds an accordion CSS class to itself element. 106.directive('accordion', function () { 107 return { 108 restrict:'EA', 109 controller:'AccordionController', 110 transclude: true, 111 replace: false, 112 templateUrl: 'template/accordion/accordion.html' 113 }; 114}) 115 116// The accordion-group directive indicates a block of html that will expand and collapse in an accordion 117.directive('accordionGroup', function() { 118 return { 119 require:'^accordion', // We need this directive to be inside an accordion 120 restrict:'EA', 121 transclude:true, // It transcludes the contents of the directive into the template 122 replace: true, // The element containing the directive will be replaced with the template 123 templateUrl:'template/accordion/accordion-group.html', 124 scope: { 125 heading: '@', // Interpolate the heading attribute onto this scope 126 isOpen: '=?', 127 isDisabled: '=?' 128 }, 129 controller: function() { 130 this.setHeading = function(element) { 131 this.heading = element; 132 }; 133 }, 134 link: function(scope, element, attrs, accordionCtrl) { 135 accordionCtrl.addGroup(scope); 136 137 scope.$watch('isOpen', function(value) { 138 if ( value ) { 139 accordionCtrl.closeOthers(scope); 140 } 141 }); 142 143 scope.toggleOpen = function() { 144 if ( !scope.isDisabled ) { 145 scope.isOpen = !scope.isOpen; 146 } 147 }; 148 } 149 }; 150}) 151 152// Use accordion-heading below an accordion-group to provide a heading containing HTML 153// <accordion-group> 154// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading> 155// </accordion-group> 156.directive('accordionHeading', function() { 157 return { 158 restrict: 'EA', 159 transclude: true, // Grab the contents to be used as the heading 160 template: '', // In effect remove this element! 161 replace: true, 162 require: '^accordionGroup', 163 link: function(scope, element, attr, accordionGroupCtrl, transclude) { 164 // Pass the heading to the accordion-group controller 165 // so that it can be transcluded into the right place in the template 166 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] 167 accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); 168 } 169 }; 170}) 171 172// Use in the accordion-group template to indicate where you want the heading to be transcluded 173// You must provide the property on the accordion-group controller that will hold the transcluded element 174// <div class="accordion-group"> 175// <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div> 176// ... 177// </div> 178.directive('accordionTransclude', function() { 179 return { 180 require: '^accordionGroup', 181 link: function(scope, element, attr, controller) { 182 scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) { 183 if ( heading ) { 184 element.html(''); 185 element.append(heading); 186 } 187 }); 188 } 189 }; 190}) 191 192; 193 194angular.module('ui.bootstrap.alert', []) 195 196.controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) { 197 $scope.closeable = 'close' in $attrs; 198 this.close = $scope.close; 199}]) 200 201.directive('alert', function () { 202 return { 203 restrict:'EA', 204 controller:'AlertController', 205 templateUrl:'template/alert/alert.html', 206 transclude:true, 207 replace:true, 208 scope: { 209 type: '@', 210 close: '&' 211 } 212 }; 213}) 214 215.directive('dismissOnTimeout', ['$timeout', function($timeout) { 216 return { 217 require: 'alert', 218 link: function(scope, element, attrs, alertCtrl) { 219 $timeout(function(){ 220 alertCtrl.close(); 221 }, parseInt(attrs.dismissOnTimeout, 10)); 222 } 223 }; 224}]); 225 226angular.module('ui.bootstrap.bindHtml', []) 227 228 .directive('bindHtmlUnsafe', function () { 229 return function (scope, element, attr) { 230 element.addClass('ng-binding').data('$binding', attr.bindHtmlUnsafe); 231 scope.$watch(attr.bindHtmlUnsafe, function bindHtmlUnsafeWatchAction(value) { 232 element.html(value || ''); 233 }); 234 }; 235 }); 236angular.module('ui.bootstrap.buttons', []) 237 238.constant('buttonConfig', { 239 activeClass: 'active', 240 toggleEvent: 'click' 241}) 242 243.controller('ButtonsController', ['buttonConfig', function(buttonConfig) { 244 this.activeClass = buttonConfig.activeClass || 'active'; 245 this.toggleEvent = buttonConfig.toggleEvent || 'click'; 246}]) 247 248.directive('btnRadio', function () { 249 return { 250 require: ['btnRadio', 'ngModel'], 251 controller: 'ButtonsController', 252 link: function (scope, element, attrs, ctrls) { 253 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 254 255 //model -> UI 256 ngModelCtrl.$render = function () { 257 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio))); 258 }; 259 260 //ui->model 261 element.bind(buttonsCtrl.toggleEvent, function () { 262 var isActive = element.hasClass(buttonsCtrl.activeClass); 263 264 if (!isActive || angular.isDefined(attrs.uncheckable)) { 265 scope.$apply(function () { 266 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio)); 267 ngModelCtrl.$render(); 268 }); 269 } 270 }); 271 } 272 }; 273}) 274 275.directive('btnCheckbox', function () { 276 return { 277 require: ['btnCheckbox', 'ngModel'], 278 controller: 'ButtonsController', 279 link: function (scope, element, attrs, ctrls) { 280 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 281 282 function getTrueValue() { 283 return getCheckboxValue(attrs.btnCheckboxTrue, true); 284 } 285 286 function getFalseValue() { 287 return getCheckboxValue(attrs.btnCheckboxFalse, false); 288 } 289 290 function getCheckboxValue(attributeValue, defaultValue) { 291 var val = scope.$eval(attributeValue); 292 return angular.isDefined(val) ? val : defaultValue; 293 } 294 295 //model -> UI 296 ngModelCtrl.$render = function () { 297 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); 298 }; 299 300 //ui->model 301 element.bind(buttonsCtrl.toggleEvent, function () { 302 scope.$apply(function () { 303 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); 304 ngModelCtrl.$render(); 305 }); 306 }); 307 } 308 }; 309}); 310 311/** 312* @ngdoc overview 313* @name ui.bootstrap.carousel
314* 315* @description 316* AngularJS version of an image carousel. 317* 318*/ 319angular.module('ui.bootstrap.carousel', []) 320.controller('CarouselController', ['$scope', '$interval', '$animate', function ($scope, $interval, $animate) { 321 var self = this, 322 slides = self.slides = $scope.slides = [], 323 currentIndex = -1, 324 currentInterval, isPlaying; 325 self.currentSlide = null; 326 327 var destroyed = false; 328 /* direction: "prev" or "next" */ 329 self.select = $scope.select = function(nextSlide, direction) { 330 var nextIndex = self.indexOfSlide(nextSlide); 331 //Decide direction if it's not given 332 if (direction === undefined) { 333 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; 334 } 335 if (nextSlide && nextSlide !== self.currentSlide) { 336 goNext(); 337 } 338 function goNext() { 339 // Scope has been destroyed, stop here. 340 if (destroyed) { return; } 341 342 angular.extend(nextSlide, {direction: direction, active: true}); 343 angular.extend(self.currentSlide || {}, {direction: direction, active: false}); 344 if ($animate.enabled() && !$scope.noTransition && nextSlide.$element) { 345 $scope.$currentTransition = true; 346 nextSlide.$element.one('$animate:close', function closeFn() { 347 $scope.$currentTransition = null; 348 }); 349 } 350 351 self.currentSlide = nextSlide; 352 currentIndex = nextIndex; 353 //every time you change slides, reset the timer 354 restartTimer(); 355 } 356 }; 357 $scope.$on('$destroy', function () { 358 destroyed = true; 359 }); 360 361 function getSlideByIndex(index) { 362 if (angular.isUndefined(slides[index].index)) { 363 return slides[index]; 364 } 365 var i, len = slides.length; 366 for (i = 0; i < slides.length; ++i) { 367 if (slides[i].index == index) { 368 return slides[i]; 369 } 370 } 371 } 372 373 self.getCurrentIndex = function() { 374 if (self.currentSlide && angular.isDefined(self.currentSlide.index)) { 375 return +self.currentSlide.index; 376 } 377 return currentIndex; 378 }; 379 380 /* Allow outside people to call indexOf on slides array */ 381 self.indexOfSlide = function(slide) { 382 return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide); 383 }; 384 385 $scope.next = function() { 386 var newIndex = (self.getCurrentIndex() + 1) % slides.length; 387 388 //Prevent this user-triggered transition from occurring if there is already one in progress 389 if (!$scope.$currentTransition) { 390 return self.select(getSlideByIndex(newIndex), 'next'); 391 } 392 }; 393 394 $scope.prev = function() { 395 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; 396 397 //Prevent this user-triggered transition from occurring if there is already one in progress 398 if (!$scope.$currentTransition) { 399 return self.select(getSlideByIndex(newIndex), 'prev'); 400 } 401 }; 402 403 $scope.isActive = function(slide) { 404 return self.currentSlide === slide; 405 }; 406 407 $scope.$watch('interval', restartTimer); 408 $scope.$on('$destroy', resetTimer); 409 410 function restartTimer() { 411 resetTimer(); 412 var interval = +$scope.interval; 413 if (!isNaN(interval) && interval > 0) { 414 currentInterval = $interval(timerFn, interval); 415 } 416 } 417 418 function resetTimer() { 419 if (currentInterval) { 420 $interval.cancel(currentInterval); 421 currentInterval = null; 422 } 423 } 424 425 function timerFn() { 426 var interval = +$scope.interval; 427 if (isPlaying && !isNaN(interval) && interval > 0) { 428 $scope.next(); 429 } else { 430 $scope.pause(); 431 } 432 } 433 434 $scope.play = function() { 435 if (!isPlaying) { 436 isPlaying = true; 437 restartTimer(); 438 } 439 }; 440 $scope.pause = function() { 441 if (!$scope.noPause) { 442 isPlaying = false; 443 resetTimer(); 444 } 445 }; 446 447 self.addSlide = function(slide, element) { 448 slide.$element = element; 449 slides.push(slide); 450 //if this is the first slide or the slide is set to active, select it 451 if(slides.length === 1 || slide.active) { 452 self.select(slides[slides.length-1]); 453 if (slides.length == 1) { 454 $scope.play(); 455 } 456 } else { 457 slide.active = false; 458 } 459 }; 460 461 self.removeSlide = function(slide) { 462 if (angular.isDefined(slide.index)) { 463 slides.sort(function(a, b) { 464 return +a.index > +b.index; 465 }); 466 } 467 //get the index of the slide inside the carousel
468 var index = slides.indexOf(slide); 469 slides.splice(index, 1); 470 if (slides.length > 0 && slide.active) { 471 if (index >= slides.length) { 472 self.select(slides[index-1]); 473 } else { 474 self.select(slides[index]); 475 } 476 } else if (currentIndex > index) { 477 currentIndex--; 478 } 479 }; 480 481}]) 482 483/** 484 * @ngdoc directive 485 * @name ui.bootstrap.carousel.directive:carousel 486 * @restrict EA 487 * 488 * @description 489 * Carousel is the outer container for a set of image 'slides' to showcase. 490 * 491 * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide. 492 * @param {boolean=} noTransition Whether to disable transitions on the carousel. 493 * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover). 494 * 495 * @example 496<example module="ui.bootstrap"> 497 <file name="index.html"> 498 <carousel> 499 <slide> 500 <img src="http://placekitten.com/150/150" style="margin:auto;"> 501 <div class="carousel-caption"> 502 <p>Beautiful!</p> 503 </div> 504 </slide> 505 <slide> 506 <img src="http://placekitten.com/100/150" style="margin:auto;"> 507 <div class="carousel-caption"> 508 <p>D'aww!</p> 509 </div> 510 </slide> 511 </carousel> 512 </file> 513 <file name="demo.css"> 514 .carousel-indicators { 515 top: auto; 516 bottom: 15px; 517 } 518 </file> 519</example> 520 */ 521.directive('carousel', [function() { 522 return { 523 restrict: 'EA', 524 transclude: true, 525 replace: true, 526 controller: 'CarouselController', 527 require: 'carousel', 528 templateUrl: 'template/carousel/carousel.html', 529 scope: { 530 interval: '=', 531 noTransition: '=', 532 noPause: '=' 533 } 534 }; 535}]) 536 537/** 538 * @ngdoc directive 539 * @name ui.bootstrap.carousel.directive:slide 540 * @restrict EA 541 * 542 * @description 543 * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element. 544 * 545 * @param {boolean=} active Model binding, whether or not this slide is currently active. 546 * @param {number=} index The index of the slide. The slides will be sorted by this parameter. 547 * 548 * @example 549<example module="ui.bootstrap"> 550 <file name="index.html"> 551<div ng-controller="CarouselDemoCtrl"> 552 <carousel> 553 <slide ng-repeat="slide in slides" active="slide.active" index="$index"> 554 <img ng-src="{{slide.image}}" style="margin:auto;"> 555 <div class="carousel-caption"> 556 <h4>Slide {{$index}}</h4> 557 <p>{{slide.text}}</p> 558 </div> 559 </slide> 560 </carousel> 561 Interval, in milliseconds: <input type="number" ng-model="myInterval"> 562 <br />Enter a negative number to stop the interval. 563</div> 564 </file> 565 <file name="script.js"> 566function CarouselDemoCtrl($scope) { 567 $scope.myInterval = 5000; 568} 569 </file> 570 <file name="demo.css"> 571 .carousel-indicators { 572 top: auto; 573 bottom: 15px; 574 } 575 </file> 576</example> 577*/ 578 579.directive('slide', function() { 580 return { 581 require: '^carousel', 582 restrict: 'EA', 583 transclude: true, 584 replace: true, 585 templateUrl: 'template/carousel/slide.html', 586 scope: { 587 active: '=?', 588 index: '=?' 589 }, 590 link: function (scope, element, attrs, carouselCtrl) { 591 carouselCtrl.addSlide(scope, element); 592 //when the scope is destroyed then remove the slide from the current slides array 593 scope.$on('$destroy', function() { 594 carouselCtrl.removeSlide(scope); 595 }); 596 597 scope.$watch('active', function(active) { 598 if (active) { 599 carouselCtrl.select(scope); 600 } 601 }); 602 } 603 }; 604}) 605 606.animation('.item', [ 607 '$animate', 608function ($animate) { 609 return { 610 beforeAddClass: function (element, className, done) { 611 // Due to transclusion, noTransition property is on parent's scope 612 if (className == 'active' && element.parent() && 613 !element.parent().scope().noTransition) { 614 var stopped = false; 615 var direction = element.isolateScope().direction; 616 var directionClass = direction == 'next' ? 'left' : 'right'; 617 element.addClass(direction); 618 $animate.addClass(element, directionClass).then(function () { 619 if (!stopped) { 620 element.removeClass(directionClass + ' ' + direction); 621 } 622 done(); 623 }); 624 625 return function () { 626 stopped = true; 627 }; 628 } 629 done(); 630 }, 631 beforeRemoveClass: function (element, className, done) { 632 // Due to transclusion, noTransition property is on parent's scope 633 if (className == 'active' && element.parent() && 634 !element.parent().scope().noTransition) { 635 var stopped = false;
636 var direction = element.isolateScope().direction; 637 var directionClass = direction == 'next' ? 'left' : 'right'; 638 $animate.addClass(element, directionClass).then(function () { 639 if (!stopped) { 640 element.removeClass(directionClass); 641 } 642 done(); 643 }); 644 return function () { 645 stopped = true; 646 }; 647 } 648 done(); 649 } 650 }; 651 652}]) 653 654 655; 656 657angular.module('ui.bootstrap.dateparser', []) 658 659.service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) { 660 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js 661 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; 662 663 this.parsers = {}; 664 665 var formatCodeToRegex = { 666 'yyyy': { 667 regex: '\\d{4}', 668 apply: function(value) { this.year = +value; } 669 }, 670 'yy': { 671 regex: '\\d{2}', 672 apply: function(value) { this.year = +value + 2000; } 673 }, 674 'y': { 675 regex: '\\d{1,4}', 676 apply: function(value) { this.year = +value; } 677 }, 678 'MMMM': { 679 regex: $locale.DATETIME_FORMATS.MONTH.join('|'), 680 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); } 681 }, 682 'MMM': { 683 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), 684 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); } 685 }, 686 'MM': { 687 regex: '0[1-9]|1[0-2]', 688 apply: function(value) { this.month = value - 1; } 689 }, 690 'M': { 691 regex: '[1-9]|1[0-2]', 692 apply: function(value) { this.month = value - 1; } 693 }, 694 'dd': { 695 regex: '[0-2][0-9]{1}|3[0-1]{1}', 696 apply: function(value) { this.date = +value; } 697 }, 698 'd': { 699 regex: '[1-2]?[0-9]{1}|3[0-1]{1}', 700 apply: function(value) { this.date = +value; } 701 }, 702 'EEEE': { 703 regex: $locale.DATETIME_FORMATS.DAY.join('|') 704 }, 705 'EEE': { 706 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|') 707 }, 708 'HH': { 709 regex: '(?:0|1)[0-9]|2[0-3]', 710 apply: function(value) { this.hours = +value; } 711 }, 712 'H': { 713 regex: '1?[0-9]|2[0-3]', 714 apply: function(value) { this.hours = +value; } 715 }, 716 'mm': { 717 regex: '[0-5][0-9]', 718 apply: function(value) { this.minutes = +value; } 719 }, 720 'm': { 721 regex: '[0-9]|[1-5][0-9]', 722 apply: function(value) { this.minutes = +value; } 723 }, 724 'sss': { 725 regex: '[0-9][0-9][0-9]', 726 apply: function(value) { this.milliseconds = +value; } 727 }, 728 'ss': { 729 regex: '[0-5][0-9]', 730 apply: function(value) { this.seconds = +value; } 731 }, 732 's': { 733 regex: '[0-9]|[1-5][0-9]', 734 apply: function(value) { this.seconds = +value; } 735 } 736 }; 737 738 function createParser(format) { 739 var map = [], regex = format.split(''); 740 741 angular.forEach(formatCodeToRegex, function(data, code) { 742 var index = format.indexOf(code); 743 744 if (index > -1) { 745 format = format.split(''); 746 747 regex[index] = '(' + data.regex + ')'; 748 format[index] = '$'; // Custom symbol to define consumed part of format 749 for (var i = index + 1, n = index + code.length; i < n; i++) { 750 regex[i] = ''; 751 format[i] = '$'; 752 } 753 format = format.join(''); 754 755 map.push({ index: index, apply: data.apply }); 756 } 757 }); 758 759 return { 760 regex: new RegExp('^' + regex.join('') + '$'), 761 map: orderByFilter(map, 'index') 762 }; 763 } 764 765 this.parse = function(input, format, baseDate) { 766 if ( !angular.isString(input) || !format ) { 767 return input; 768 } 769 770 format = $locale.DATETIME_FORMATS[format] || format; 771 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); 772 773 if ( !this.parsers[format] ) { 774 this.parsers[format] = createParser(format); 775 } 776 777 var parser = this.parsers[format], 778 regex = parser.regex, 779 map = parser.map, 780 results = input.match(regex); 781 782 if ( results && results.length ) { 783 var fields, dt; 784 if (baseDate) { 785 fields = { 786 year: baseDate.getFullYear(), 787 month: baseDate.getMonth(), 788 date: baseDate.getDate(), 789 hours: baseDate.getHours(), 790 minutes: baseDate.getMinutes(), 791 seconds: baseDate.getSeconds(), 792 milliseconds: baseDate.getMilliseconds() 793 }; 794 } else { 795 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; 796 } 797 798 for( var i = 1, n = results.length; i < n; i++ ) { 799 var mapper = map[i-1]; 800 if ( mapper.apply ) { 801 mapper.apply.call(fields, results[i]); 802 } 803 } 804 805 if ( isValid(fields.year, fields.month, fields.date) ) { 806 dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds, 807 fields.milliseconds || 0); 808 } 809 810 return dt; 811 } 812 }; 813 814 // Check if date is valid for specific month (and year for February). 815 // Month: 0 = Jan, 1 = Feb, etc 816 function isValid(year, month, date) { 817 if (date < 1) { 818 return false;
819 } 820 821 if ( month === 1 && date > 28) { 822 return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0); 823 } 824 825 if ( month === 3 || month === 5 || month === 8 || month === 10) { 826 return date < 31; 827 } 828 829 return true; 830 } 831}]); 832 833angular.module('ui.bootstrap.position', []) 834 835/** 836 * A set of utility methods that can be use to retrieve position of DOM elements. 837 * It is meant to be used where we need to absolute-position DOM elements in 838 * relation to other, existing elements (this is the case for tooltips, popovers, 839 * typeahead suggestions etc.). 840 */ 841 .factory('$position', ['$document', '$window', function ($document, $window) { 842 843 function getStyle(el, cssprop) { 844 if (el.currentStyle) { //IE 845 return el.currentStyle[cssprop]; 846 } else if ($window.getComputedStyle) { 847 return $window.getComputedStyle(el)[cssprop]; 848 } 849 // finally try and get inline style 850 return el.style[cssprop]; 851 } 852 853 /** 854 * Checks if a given element is statically positioned 855 * @param element - raw DOM element 856 */ 857 function isStaticPositioned(element) { 858 return (getStyle(element, 'position') || 'static' ) === 'static'; 859 } 860 861 /** 862 * returns the closest, non-statically positioned parentOffset of a given element 863 * @param element 864 */ 865 var parentOffsetEl = function (element) { 866 var docDomEl = $document[0]; 867 var offsetParent = element.offsetParent || docDomEl; 868 while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) { 869 offsetParent = offsetParent.offsetParent; 870 } 871 return offsetParent || docDomEl; 872 }; 873 874 return { 875 /** 876 * Provides read-only equivalent of jQuery's position function: 877 * http://api.jquery.com/position/ 878 */ 879 position: function (element) { 880 var elBCR = this.offset(element); 881 var offsetParentBCR = { top: 0, left: 0 }; 882 var offsetParentEl = parentOffsetEl(element[0]); 883 if (offsetParentEl != $document[0]) { 884 offsetParentBCR = this.offset(angular.element(offsetParentEl)); 885 offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop; 886 offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft; 887 } 888 889 var boundingClientRect = element[0].getBoundingClientRect(); 890 return { 891 width: boundingClientRect.width || element.prop('offsetWidth'), 892 height: boundingClientRect.height || element.prop('offsetHeight'), 893 top: elBCR.top - offsetParentBCR.top, 894 left: elBCR.left - offsetParentBCR.left 895 }; 896 }, 897 898 /** 899 * Provides read-only equivalent of jQuery's offset function: 900 * http://api.jquery.com/offset/ 901 */ 902 offset: function (element) { 903 var boundingClientRect = element[0].getBoundingClientRect(); 904 return { 905 width: boundingClientRect.width || element.prop('offsetWidth'), 906 height: boundingClientRect.height || element.prop('offsetHeight'), 907 top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop), 908 left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft) 909 }; 910 }, 911 912 /** 913 * Provides coordinates for the targetEl in relation to hostEl 914 */ 915 positionElements: function (hostEl, targetEl, positionStr, appendToBody) { 916 917 var positionStrParts = positionStr.split('-'); 918 var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center'; 919 920 var hostElPos, 921 targetElWidth, 922 targetElHeight, 923 targetElPos; 924 925 hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); 926 927 targetElWidth = targetEl.prop('offsetWidth'); 928 targetElHeight = targetEl.prop('offsetHeight'); 929 930 var shiftWidth = { 931 center: function () { 932 return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; 933 }, 934 left: function () { 935 return hostElPos.left; 936 }, 937 right: function () { 938 return hostElPos.left + hostElPos.width; 939 } 940 }; 941 942 var shiftHeight = { 943 center: function () { 944 return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; 945 }, 946 top: function () { 947 return hostElPos.top; 948 }, 949 bottom: function () { 950 return hostElPos.top + hostElPos.height; 951 } 952 }; 953 954 switch (pos0) { 955 case 'right': 956 targetElPos = { 957 top: shiftHeight[pos1](), 958 left: shiftWidth[pos0]() 959 }; 960 break; 961 case 'left': 962 targetElPos = { 963 top: shiftHeight[pos1](), 964 left: hostElPos.left - targetElWidth 965 }; 966 break; 967 case 'bottom': 968 targetElPos = { 969 top: shiftHeight[pos0](), 970 left: shiftWidth[pos1]() 971 }; 972 break; 973 default: 974 targetElPos = { 975 top: hostElPos.top - targetElHeight, 976 left: shiftWidth[pos1]() 977 }; 978 break; 979 } 980 981 return targetElPos; 982 } 983 }; 984 }]); 985 986angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position']) 987 988.constant('datepickerConfig', { 989 formatDay: 'dd', 990 formatMonth: 'MMMM', 991 formatYear: 'yyyy', 992 formatDayHeader: 'EEE', 993 formatDayTitle: 'MMMM yyyy', 994 formatMonthTitle: 'yyyy', 995 datepickerMode: 'day', 996 minMode: 'day',
997 maxMode: 'year', 998 showWeeks: true, 999 startingDay: 0, 1000 yearRange: 20, 1001 minDate: null, 1002 maxDate: null, 1003 shortcutPropagation: false 1004}) 1005 1006.controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) { 1007 var self = this, 1008 ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl; 1009 1010 // Modes chain 1011 this.modes = ['day', 'month', 'year']; 1012 1013 // Configuration attributes 1014 angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle', 1015 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function( key, index ) { 1016 self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key]; 1017 }); 1018 1019 // Watchable date attributes 1020 angular.forEach(['minDate', 'maxDate'], function( key ) { 1021 if ( $attrs[key] ) { 1022 $scope.$parent.$watch($parse($attrs[key]), function(value) { 1023 self[key] = value ? new Date(value) : null; 1024 self.refreshView(); 1025 }); 1026 } else { 1027 self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null; 1028 } 1029 }); 1030 1031 $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode; 1032 $scope.maxMode = self.maxMode; 1033 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); 1034 1035 if(angular.isDefined($attrs.initDate)) { 1036 this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date(); 1037 $scope.$parent.$watch($attrs.initDate, function(initDate){ 1038 if(initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)){ 1039 self.activeDate = initDate; 1040 self.refreshView(); 1041 } 1042 }); 1043 } else { 1044 this.activeDate = new Date(); 1045 } 1046 1047 $scope.isActive = function(dateObject) { 1048 if (self.compare(dateObject.date, self.activeDate) === 0) { 1049 $scope.activeDateId = dateObject.uid; 1050 return true; 1051 } 1052 return false; 1053 }; 1054 1055 this.init = function( ngModelCtrl_ ) { 1056 ngModelCtrl = ngModelCtrl_; 1057 1058 ngModelCtrl.$render = function() { 1059 self.render(); 1060 }; 1061 }; 1062 1063 this.render = function() { 1064 if ( ngModelCtrl.$viewValue ) { 1065 var date = new Date( ngModelCtrl.$viewValue ), 1066 isValid = !isNaN(date); 1067 1068 if ( isValid ) { 1069 this.activeDate = date; 1070 } else { 1071 $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.'); 1072 } 1073 ngModelCtrl.$setValidity('date', isValid); 1074 } 1075 this.refreshView(); 1076 }; 1077 1078 this.refreshView = function() { 1079 if ( this.element ) { 1080 this._refreshView(); 1081 1082 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; 1083 ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date))); 1084 } 1085 }; 1086 1087 this.createDateObject = function(date, format) { 1088 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; 1089 return { 1090 date: date, 1091 label: dateFilter(date, format), 1092 selected: model && this.compare(date, model) === 0, 1093 disabled: this.isDisabled(date), 1094 current: this.compare(date, new Date()) === 0, 1095 customClass: this.customClass(date) 1096 }; 1097 }; 1098 1099 this.isDisabled = function( date ) { 1100 return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}))); 1101 }; 1102 1103 this.customClass = function( date ) { 1104 return $scope.customClass({date: date, mode: $scope.datepickerMode}); 1105 }; 1106 1107 // Split array into smaller arrays 1108 this.split = function(arr, size) { 1109 var arrays = []; 1110 while (arr.length > 0) { 1111 arrays.push(arr.splice(0, size)); 1112 } 1113 return arrays; 1114 }; 1115 1116 $scope.select = function( date ) { 1117 if ( $scope.datepickerMode === self.minMode ) { 1118 var dt = ngModelCtrl.$viewValue ? new Date( ngModelCtrl.$viewValue ) : new Date(0, 0, 0, 0, 0, 0, 0); 1119 dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() ); 1120 ngModelCtrl.$setViewValue( dt ); 1121 ngModelCtrl.$render(); 1122 } else { 1123 self.activeDate = date; 1124 $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ]; 1125 } 1126 }; 1127 1128 $scope.move = function( direction ) { 1129 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1130 month = self.activeDate.getMonth() + direction * (self.step.months || 0); 1131 self.activeDate.setFullYear(year, month, 1); 1132 self.refreshView(); 1133 }; 1134 1135 $scope.toggleMode = function( direction ) { 1136 direction = direction || 1; 1137 1138 if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) { 1139 return; 1140 } 1141 1142 $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ]; 1143 }; 1144 1145 // Key event mapper 1146 $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' }; 1147 1148 var focusElement = function() { 1149 $timeout(function() { 1150 self.element[0].focus(); 1151 }, 0 , false); 1152 }; 1153 1154 // Listen for focus requests from popup directive 1155 $scope.$on('datepicker.focus', focusElement); 1156 1157 $scope.keydown = function( evt ) { 1158 var key = $scope.keys[evt.which]; 1159 1160 if ( !key || evt.shiftKey || evt.altKey ) { 1161 return; 1162 } 1163 1164 evt.preventDefault(); 1165 if(!self.shortcutPropagation){ 1166 evt.stopPropagation(); 1167 } 1168 1169 if (key === 'enter' || key === 'space') { 1170 if ( self.isDisabled(self.activeDate)) { 1171 return; // do nothing 1172 } 1173 $scope.select(self.activeDate); 1174 focusElement(); 1175 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { 1176 $scope.toggleMode(key === 'up' ? 1 : -1); 1177 focusElement(); 1178 } else { 1179 self.handleKeyDown(key, evt); 1180 self.refreshView(); 1181 } 1182 }; 1183}]) 1184 1185.directive( 'datepicker', function () { 1186 return { 1187 restrict: 'EA', 1188 replace: true, 1189 templateUrl: 'template/datepicker/datepicker.html', 1190 scope: { 1191 datepickerMode: '=?', 1192 dateDisabled: '&', 1193 customClass: '&', 1194 shortcutPropagation: '&?' 1195 }, 1196 require: ['datepicker', '?^ngModel'], 1197 controller: 'DatepickerController', 1198 link: function(scope, element, attrs, ctrls) { 1199 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 1200 1201 if ( ngModelCtrl ) { 1202 datepickerCtrl.init( ngModelCtrl ); 1203 } 1204 } 1205 }; 1206}) 1207 1208.directive('daypicker', ['dateFilter', function (dateFilter) { 1209 return { 1210 restrict: 'EA', 1211 replace: true, 1212 templateUrl: 'template/datepicker/day.html', 1213 require: '^datepicker', 1214 link: function(scope, element, attrs, ctrl) { 1215 scope.showWeeks = ctrl.showWeeks; 1216 1217 ctrl.step = { months: 1 }; 1218 ctrl.element = element; 1219 1220 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 1221 function getDaysInMonth( year, month ) { 1222 return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month]; 1223 } 1224 1225 function getDates(startDate, n) { 1226 var dates = new Array(n), current = new Date(startDate), i = 0; 1227 current.setHours(12); // Prevent repeated dates because of timezone bug 1228 while ( i < n ) { 1229 dates[i++] = new Date(current); 1230 current.setDate( current.getDate() + 1 ); 1231 } 1232 return dates; 1233 } 1234 1235 ctrl._refreshView = function() { 1236 var year = ctrl.activeDate.getFullYear(), 1237 month = ctrl.activeDate.getMonth(), 1238 firstDayOfMonth = new Date(year, month, 1), 1239 difference = ctrl.startingDay - firstDayOfMonth.getDay(), 1240 numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference, 1241 firstDate = new Date(firstDayOfMonth); 1242 1243 if ( numDisplayedFromPreviousMonth > 0 ) { 1244 firstDate.setDate( - numDisplayedFromPreviousMonth + 1 ); 1245 } 1246 1247 // 42 is the number of days on a six-month calendar 1248 var days = getDates(firstDate, 42); 1249 for (var i = 0; i < 42; i ++) { 1250 days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), { 1251 secondary: days[i].getMonth() !== month, 1252 uid: scope.uniqueId + '-' + i 1253 }); 1254 } 1255 1256 scope.labels = new Array(7); 1257 for (var j = 0; j < 7; j++) { 1258 scope.labels[j] = { 1259 abbr: dateFilter(days[j].date, ctrl.formatDayHeader), 1260 full: dateFilter(days[j].date, 'EEEE') 1261 }; 1262 } 1263 1264 scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle); 1265 scope.rows = ctrl.split(days, 7); 1266 1267 if ( scope.showWeeks ) { 1268 scope.weekNumbers = []; 1269 var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7, 1270 numWeeks = scope.rows.length; 1271 for (var curWeek = 0; curWeek < numWeeks; curWeek++) { 1272 scope.weekNumbers.push( 1273 getISO8601WeekNumber( scope.rows[curWeek][thursdayIndex].date )); 1274 } 1275 } 1276 }; 1277 1278 ctrl.compare = function(date1, date2) { 1279 return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) ); 1280 }; 1281 1282 function getISO8601WeekNumber(date) { 1283 var checkDate = new Date(date); 1284 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday 1285 var time = checkDate.getTime(); 1286 checkDate.setMonth(0); // Compare with Jan 1 1287 checkDate.setDate(1); 1288 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; 1289 } 1290 1291 ctrl.handleKeyDown = function( key, evt ) { 1292 var date = ctrl.activeDate.getDate(); 1293 1294 if (key === 'left') { 1295 date = date - 1; // up 1296 } else if (key === 'up') { 1297 date = date - 7; // down 1298 } else if (key === 'right') { 1299 date = date + 1; // down 1300 } else if (key === 'down') { 1301 date = date + 7; 1302 }
1302 else if (key === 'pageup' || key === 'pagedown') { 1303 var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); 1304 ctrl.activeDate.setMonth(month, 1); 1305 date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date); 1306 } else if (key === 'home') { 1307 date = 1; 1308 } else if (key === 'end') { 1309 date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()); 1310 } 1311 ctrl.activeDate.setDate(date); 1312 }; 1313 1314 ctrl.refreshView(); 1315 } 1316 }; 1317}]) 1318 1319.directive('monthpicker', ['dateFilter', function (dateFilter) { 1320 return { 1321 restrict: 'EA', 1322 replace: true, 1323 templateUrl: 'template/datepicker/month.html', 1324 require: '^datepicker', 1325 link: function(scope, element, attrs, ctrl) { 1326 ctrl.step = { years: 1 }; 1327 ctrl.element = element; 1328 1329 ctrl._refreshView = function() { 1330 var months = new Array(12), 1331 year = ctrl.activeDate.getFullYear(); 1332 1333 for ( var i = 0; i < 12; i++ ) { 1334 months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), { 1335 uid: scope.uniqueId + '-' + i 1336 }); 1337 } 1338 1339 scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle); 1340 scope.rows = ctrl.split(months, 3); 1341 }; 1342 1343 ctrl.compare = function(date1, date2) { 1344 return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() ); 1345 }; 1346 1347 ctrl.handleKeyDown = function( key, evt ) { 1348 var date = ctrl.activeDate.getMonth(); 1349 1350 if (key === 'left') { 1351 date = date - 1; // up 1352 } else if (key === 'up') { 1353 date = date - 3; // down 1354 } else if (key === 'right') { 1355 date = date + 1; // down 1356 } else if (key === 'down') { 1357 date = date + 3; 1358 } else if (key === 'pageup' || key === 'pagedown') { 1359 var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); 1360 ctrl.activeDate.setFullYear(year); 1361 } else if (key === 'home') { 1362 date = 0; 1363 } else if (key === 'end') { 1364 date = 11; 1365 } 1366 ctrl.activeDate.setMonth(date); 1367 }; 1368 1369 ctrl.refreshView(); 1370 } 1371 }; 1372}]) 1373 1374.directive('yearpicker', ['dateFilter', function (dateFilter) { 1375 return { 1376 restrict: 'EA', 1377 replace: true, 1378 templateUrl: 'template/datepicker/year.html', 1379 require: '^datepicker', 1380 link: function(scope, element, attrs, ctrl) { 1381 var range = ctrl.yearRange; 1382 1383 ctrl.step = { years: range }; 1384 ctrl.element = element; 1385 1386 function getStartingYear( year ) { 1387 return parseInt((year - 1) / range, 10) * range + 1; 1388 } 1389 1390 ctrl._refreshView = function() { 1391 var years = new Array(range); 1392 1393 for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) { 1394 years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), { 1395 uid: scope.uniqueId + '-' + i 1396 }); 1397 } 1398 1399 scope.title = [years[0].label, years[range - 1].label].join(' - '); 1400 scope.rows = ctrl.split(years, 5); 1401 }; 1402 1403 ctrl.compare = function(date1, date2) { 1404 return date1.getFullYear() - date2.getFullYear(); 1405 }; 1406 1407 ctrl.handleKeyDown = function( key, evt ) { 1408 var date = ctrl.activeDate.getFullYear(); 1409 1410 if (key === 'left') { 1411 date = date - 1; // up 1412 } else if (key === 'up') { 1413 date = date - 5; // down 1414 } else if (key === 'right') { 1415 date = date + 1; // down 1416 } else if (key === 'down') { 1417 date = date + 5; 1418 } else if (key === 'pageup' || key === 'pagedown') { 1419 date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years; 1420 } else if (key === 'home') { 1421 date = getStartingYear( ctrl.activeDate.getFullYear() ); 1422 } else if (key === 'end') { 1423 date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1; 1424 } 1425 ctrl.activeDate.setFullYear(date); 1426 }; 1427 1428 ctrl.refreshView(); 1429 } 1430 }; 1431}]) 1432 1433.constant('datepickerPopupConfig', { 1434 datepickerPopup: 'yyyy-MM-dd', 1435 html5Types: { 1436 date: 'yyyy-MM-dd', 1437 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', 1438 'month': 'yyyy-MM' 1439 }, 1440 currentText: 'Today', 1441 clearText: 'Clear', 1442 closeText: 'Done', 1443 closeOnDateSelection: true, 1444 appendToBody: false, 1445 showButtonBar: true 1446}) 1447 1448.directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', 1449function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) { 1450 return { 1451 restrict: 'EA', 1452 require: 'ngModel', 1453 scope: { 1454 isOpen: '=?', 1455 currentText: '@', 1456 clearText: '@', 1457 closeText: '@', 1458 dateDisabled: '&', 1459 customClass: '&' 1460 }, 1461 link: function(scope, element, attrs, ngModel) { 1462 var dateFormat, 1463 closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection, 1464 appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody; 1465 1466 scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar; 1467 1468 scope.getText = function( key ) { 1469 return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; 1470 }; 1471 1472 var isHtml5DateInput = false;
1473 if (datepickerPopupConfig.html5Types[attrs.type]) { 1474 dateFormat = datepickerPopupConfig.html5Types[attrs.type]; 1475 isHtml5DateInput = true; 1476 } else { 1477 dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup; 1478 attrs.$observe('datepickerPopup', function(value, oldValue) { 1479 var newDateFormat = value || datepickerPopupConfig.datepickerPopup; 1480 // Invalidate the $modelValue to ensure that formatters re-run 1481 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 1482 if (newDateFormat !== dateFormat) { 1483 dateFormat = newDateFormat; 1484 ngModel.$modelValue = null; 1485 1486 if (!dateFormat) { 1487 throw new Error('datepickerPopup must have a date format specified.'); 1488 } 1489 } 1490 }); 1491 } 1492 1493 if (!dateFormat) { 1494 throw new Error('datepickerPopup must have a date format specified.'); 1495 } 1496 1497 if (isHtml5DateInput && attrs.datepickerPopup) { 1498 throw new Error('HTML5 date input types do not support custom formats.'); 1499 } 1500 1501 // popup element used to display calendar 1502 var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>'); 1503 popupEl.attr({ 1504 'ng-model': 'date', 1505 'ng-change': 'dateSelection()' 1506 }); 1507 1508 function cameltoDash( string ){ 1509 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); 1510 } 1511 1512 // datepicker element 1513 var datepickerEl = angular.element(popupEl.children()[0]); 1514 if (isHtml5DateInput) { 1515 if (attrs.type == 'month') { 1516 datepickerEl.attr('datepicker-mode', '"month"'); 1517 datepickerEl.attr('min-mode', 'month'); 1518 } 1519 } 1520 1521 if ( attrs.datepickerOptions ) { 1522 var options = scope.$parent.$eval(attrs.datepickerOptions); 1523 if(options.initDate) { 1524 scope.initDate = options.initDate; 1525 datepickerEl.attr( 'init-date', 'initDate' ); 1526 delete options.initDate; 1527 } 1528 angular.forEach(options, function( value, option ) { 1529 datepickerEl.attr( cameltoDash(option), value ); 1530 }); 1531 } 1532 1533 scope.watchData = {}; 1534 angular.forEach(['minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function( key ) { 1535 if ( attrs[key] ) { 1536 var getAttribute = $parse(attrs[key]); 1537 scope.$parent.$watch(getAttribute, function(value){ 1538 scope.watchData[key] = value; 1539 }); 1540 datepickerEl.attr(cameltoDash(key), 'watchData.' + key); 1541 1542 // Propagate changes from datepicker to outside 1543 if ( key === 'datepickerMode' ) { 1544 var setAttribute = getAttribute.assign; 1545 scope.$watch('watchData.' + key, function(value, oldvalue) { 1546 if ( value !== oldvalue ) { 1547 setAttribute(scope.$parent, value); 1548 } 1549 }); 1550 } 1551 } 1552 }); 1553 if (attrs.dateDisabled) { 1554 datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })'); 1555 } 1556 1557 if (attrs.showWeeks) { 1558 datepickerEl.attr('show-weeks', attrs.showWeeks); 1559 } 1560 1561 if (attrs.customClass){ 1562 datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })'); 1563 } 1564 1565 function parseDate(viewValue) { 1566 if (angular.isNumber(viewValue)) { 1567 // presumably timestamp to date object 1568 viewValue = new Date(viewValue); 1569 } 1570 1571 if (!viewValue) { 1572 return null; 1573 } else if (angular.isDate(viewValue) && !isNaN(viewValue)) { 1574 return viewValue; 1575 } else if (angular.isString(viewValue)) { 1576 var date = dateParser.parse(viewValue, dateFormat, scope.date) || new Date(viewValue); 1577 if (isNaN(date)) { 1578 return undefined; 1579 } else { 1580 return date; 1581 } 1582 } else { 1583 return undefined; 1584 } 1585 } 1586 1587 function validator(modelValue, viewValue) { 1588 var value = modelValue || viewValue; 1589 if (angular.isNumber(value)) { 1590 value = new Date(value); 1591 } 1592 if (!value) { 1593 return true; 1594 } else if (angular.isDate(value) && !isNaN(value)) { 1595 return true; 1596 } else if (angular.isString(value)) { 1597 var date = dateParser.parse(value, dateFormat) || new Date(value); 1598 return !isNaN(date); 1599 } else { 1600 return false;
1601 } 1602 } 1603 1604 if (!isHtml5DateInput) { 1605 // Internal API to maintain the correct ng-invalid-[key] class 1606 ngModel.$$parserName = 'date'; 1607 ngModel.$validators.date = validator; 1608 ngModel.$parsers.unshift(parseDate); 1609 ngModel.$formatters.push(function (value) { 1610 scope.date = value; 1611 return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat); 1612 }); 1613 } 1614 else { 1615 ngModel.$formatters.push(function (value) { 1616 scope.date = value; 1617 return value; 1618 }); 1619 } 1620 1621 // Inner change 1622 scope.dateSelection = function(dt) { 1623 if (angular.isDefined(dt)) { 1624 scope.date = dt; 1625 } 1626 var date = scope.date ? dateFilter(scope.date, dateFormat) : ''; 1627 element.val(date); 1628 ngModel.$setViewValue(date); 1629 1630 if ( closeOnDateSelection ) { 1631 scope.isOpen = false; 1632 element[0].focus(); 1633 } 1634 }; 1635 1636 // Detect changes in the view from the text box 1637 ngModel.$viewChangeListeners.push(function () { 1638 scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date) || new Date(ngModel.$viewValue); 1639 }); 1640 1641 var documentClickBind = function(event) { 1642 if (scope.isOpen && event.target !== element[0]) { 1643 scope.$apply(function() { 1644 scope.isOpen = false; 1645 }); 1646 } 1647 }; 1648 1649 var keydown = function(evt, noApply) { 1650 scope.keydown(evt); 1651 }; 1652 element.bind('keydown', keydown); 1653 1654 scope.keydown = function(evt) { 1655 if (evt.which === 27) { 1656 evt.preventDefault(); 1657 if (scope.isOpen) { 1658 evt.stopPropagation(); 1659 } 1660 scope.close(); 1661 } else if (evt.which === 40 && !scope.isOpen) { 1662 scope.isOpen = true; 1663 } 1664 }; 1665 1666 scope.$watch('isOpen', function(value) { 1667 if (value) { 1668 scope.$broadcast('datepicker.focus'); 1669 scope.position = appendToBody ? $position.offset(element) : $position.position(element); 1670 scope.position.top = scope.position.top + element.prop('offsetHeight'); 1671 1672 $document.bind('click', documentClickBind); 1673 } else { 1674 $document.unbind('click', documentClickBind); 1675 } 1676 }); 1677 1678 scope.select = function( date ) { 1679 if (date === 'today') { 1680 var today = new Date(); 1681 if (angular.isDate(scope.date)) { 1682 date = new Date(scope.date); 1683 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); 1684 } else { 1685 date = new Date(today.setHours(0, 0, 0, 0)); 1686 } 1687 } 1688 scope.dateSelection( date ); 1689 }; 1690 1691 scope.close = function() { 1692 scope.isOpen = false; 1693 element[0].focus(); 1694 }; 1695 1696 var $popup = $compile(popupEl)(scope); 1697 // Prevent jQuery cache memory leak (template is now redundant after linking) 1698 popupEl.remove(); 1699 1700 if ( appendToBody ) { 1701 $document.find('body').append($popup); 1702 } else { 1703 element.after($popup); 1704 } 1705 1706 scope.$on('$destroy', function() { 1707 $popup.remove(); 1708 element.unbind('keydown', keydown); 1709 $document.unbind('click', documentClickBind); 1710 }); 1711 } 1712 }; 1713}]) 1714 1715.directive('datepickerPopupWrap', function() { 1716 return { 1717 restrict:'EA', 1718 replace: true, 1719 transclude: true, 1720 templateUrl: 'template/datepicker/popup.html', 1721 link:function (scope, element, attrs) { 1722 element.bind('click', function(event) { 1723 event.preventDefault(); 1724 event.stopPropagation(); 1725 }); 1726 } 1727 }; 1728}); 1729 1730angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) 1731 1732.constant('dropdownConfig', { 1733 openClass: 'open' 1734}) 1735 1736.service('dropdownService', ['$document', '$rootScope', function($document, $rootScope) { 1737 var openScope = null; 1738 1739 this.open = function( dropdownScope ) { 1740 if ( !openScope ) { 1741 $document.bind('click', closeDropdown); 1742 $document.bind('keydown', escapeKeyBind); 1743 } 1744 1745 if ( openScope && openScope !== dropdownScope ) { 1746 openScope.isOpen = false;
1747 } 1748 1749 openScope = dropdownScope; 1750 }; 1751 1752 this.close = function( dropdownScope ) { 1753 if ( openScope === dropdownScope ) { 1754 openScope = null; 1755 $document.unbind('click', closeDropdown); 1756 $document.unbind('keydown', escapeKeyBind); 1757 } 1758 }; 1759 1760 var closeDropdown = function( evt ) { 1761 // This method may still be called during the same mouse event that 1762 // unbound this event handler. So check openScope before proceeding. 1763 if (!openScope) { return; } 1764 1765 if( evt && openScope.getAutoClose() === 'disabled' ) { return ; } 1766 1767 var toggleElement = openScope.getToggleElement(); 1768 if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) { 1769 return; 1770 } 1771 1772 var $element = openScope.getElement(); 1773 if( evt && openScope.getAutoClose() === 'outsideClick' && $element && $element[0].contains(evt.target) ) { 1774 return; 1775 } 1776 1777 openScope.isOpen = false; 1778 1779 if (!$rootScope.$$phase) { 1780 openScope.$apply(); 1781 } 1782 }; 1783 1784 var escapeKeyBind = function( evt ) { 1785 if ( evt.which === 27 ) { 1786 openScope.focusToggleElement(); 1787 closeDropdown(); 1788 } 1789 }; 1790}]) 1791 1792.controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', '$position', '$document', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate, $position, $document) { 1793 var self = this, 1794 scope = $scope.$new(), // create a child scope so we are not polluting original one 1795 openClass = dropdownConfig.openClass, 1796 getIsOpen, 1797 setIsOpen = angular.noop, 1798 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, 1799 appendToBody = false; 1800 1801 this.init = function( element ) { 1802 self.$element = element; 1803 1804 if ( $attrs.isOpen ) { 1805 getIsOpen = $parse($attrs.isOpen); 1806 setIsOpen = getIsOpen.assign; 1807 1808 $scope.$watch(getIsOpen, function(value) { 1809 scope.isOpen = !!value; 1810 }); 1811 } 1812 1813 appendToBody = angular.isDefined($attrs.dropdownAppendToBody); 1814 1815 if ( appendToBody && self.dropdownMenu ) { 1816 $document.find('body').append( self.dropdownMenu ); 1817 element.on('$destroy', function handleDestroyEvent() { 1818 self.dropdownMenu.remove(); 1819 }); 1820 } 1821 }; 1822 1823 this.toggle = function( open ) { 1824 return scope.isOpen = arguments.length ? !!open : !scope.isOpen; 1825 }; 1826 1827 // Allow other directives to watch status 1828 this.isOpen = function() { 1829 return scope.isOpen; 1830 }; 1831 1832 scope.getToggleElement = function() { 1833 return self.toggleElement; 1834 }; 1835 1836 scope.getAutoClose = function() { 1837 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' 1838 }; 1839 1840 scope.getElement = function() { 1841 return self.$element; 1842 }; 1843 1844 scope.focusToggleElement = function() { 1845 if ( self.toggleElement ) { 1846 self.toggleElement[0].focus(); 1847 } 1848 }; 1849 1850 scope.$watch('isOpen', function( isOpen, wasOpen ) { 1851 if ( appendToBody && self.dropdownMenu ) { 1852 var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true); 1853 self.dropdownMenu.css({ 1854 top: pos.top + 'px', 1855 left: pos.left + 'px', 1856 display: isOpen ? 'block' : 'none' 1857 }); 1858 } 1859 1860 $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass); 1861 1862 if ( isOpen ) { 1863 scope.focusToggleElement(); 1864 dropdownService.open( scope ); 1865 } else { 1866 dropdownService.close( scope ); 1867 } 1868 1869 setIsOpen($scope, isOpen); 1870 if (angular.isDefined(isOpen) && isOpen !== wasOpen) { 1871 toggleInvoker($scope, { open: !!isOpen }); 1872 } 1873 }); 1874 1875 $scope.$on('$locationChangeSuccess', function() { 1876 scope.isOpen = false; 1877 }); 1878 1879 $scope.$on('$destroy', function() { 1880 scope.$destroy(); 1881 }); 1882}]) 1883 1884.directive('dropdown', function() { 1885 return { 1886 controller: 'DropdownController', 1887 link: function(scope, element, attrs, dropdownCtrl) { 1888 dropdownCtrl.init( element ); 1889 } 1890 }; 1891}) 1892 1893.directive('dropdownMenu', function() { 1894 return { 1895 restrict: 'AC', 1896 require: '?^dropdown', 1897 link: function(scope, element, attrs, dropdownCtrl) { 1898 if ( !dropdownCtrl ) { 1899 return; 1900 } 1901 dropdownCtrl.dropdownMenu = element; 1902 } 1903 }; 1904}) 1905 1906.directive('dropdownToggle', function() { 1907 return { 1908 require: '?^dropdown', 1909 link: function(scope, element, attrs, dropdownCtrl) { 1910 if ( !dropdownCtrl ) { 1911 return; 1912 } 1913 1914 dropdownCtrl.toggleElement = element; 1915 1916 var toggleDropdown = function(event) { 1917 event.preventDefault(); 1918 1919 if ( !element.hasClass('disabled') && !attrs.disabled ) { 1920 scope.$apply(function() {
1921 dropdownCtrl.toggle(); 1922 }); 1923 } 1924 }; 1925 1926 element.bind('click', toggleDropdown); 1927 1928 // WAI-ARIA 1929 element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); 1930 scope.$watch(dropdownCtrl.isOpen, function( isOpen ) { 1931 element.attr('aria-expanded', !!isOpen); 1932 }); 1933 1934 scope.$on('$destroy', function() { 1935 element.unbind('click', toggleDropdown); 1936 }); 1937 } 1938 }; 1939}); 1940 1941angular.module('ui.bootstrap.modal', []) 1942 1943/** 1944 * A helper, internal data structure that acts as a map but also allows getting / removing 1945 * elements in the LIFO order 1946 */ 1947 .factory('$$stackedMap', function () { 1948 return { 1949 createNew: function () { 1950 var stack = []; 1951 1952 return { 1953 add: function (key, value) { 1954 stack.push({ 1955 key: key, 1956 value: value 1957 }); 1958 }, 1959 get: function (key) { 1960 for (var i = 0; i < stack.length; i++) { 1961 if (key == stack[i].key) { 1962 return stack[i]; 1963 } 1964 } 1965 }, 1966 keys: function() { 1967 var keys = []; 1968 for (var i = 0; i < stack.length; i++) { 1969 keys.push(stack[i].key); 1970 } 1971 return keys; 1972 }, 1973 top: function () { 1974 return stack[stack.length - 1]; 1975 }, 1976 remove: function (key) { 1977 var idx = -1; 1978 for (var i = 0; i < stack.length; i++) { 1979 if (key == stack[i].key) { 1980 idx = i; 1981 break; 1982 } 1983 } 1984 return stack.splice(idx, 1)[0]; 1985 }, 1986 removeTop: function () { 1987 return stack.splice(stack.length - 1, 1)[0]; 1988 }, 1989 length: function () { 1990 return stack.length; 1991 } 1992 }; 1993 } 1994 }; 1995 }) 1996 1997/** 1998 * A helper directive for the $modal service. It creates a backdrop element. 1999 */ 2000 .directive('modalBackdrop', ['$timeout', function ($timeout) { 2001 return { 2002 restrict: 'EA', 2003 replace: true, 2004 templateUrl: 'template/modal/backdrop.html',
2005 compile: function (tElement, tAttrs) { 2006 tElement.addClass(tAttrs.backdropClass); 2007 return linkFn; 2008 } 2009 }; 2010 2011 function linkFn(scope, element, attrs) { 2012 scope.animate = false; 2013 2014 //trigger CSS transitions 2015 $timeout(function () { 2016 scope.animate = true; 2017 }); 2018 } 2019 }]) 2020 2021 .directive('modalWindow', ['$modalStack', '$q', function ($modalStack, $q) { 2022 return { 2023 restrict: 'EA', 2024 scope: { 2025 index: '@', 2026 animate: '=' 2027 }, 2028 replace: true, 2029 transclude: true, 2030 templateUrl: function(tElement, tAttrs) { 2031 return tAttrs.templateUrl || 'template/modal/window.html'; 2032 }, 2033 link: function (scope, element, attrs) { 2034 element.addClass(attrs.windowClass || ''); 2035 scope.size = attrs.size; 2036 2037 scope.close = function (evt) { 2038 var modal = $modalStack.getTop(); 2039 if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) { 2040 evt.preventDefault(); 2041 evt.stopPropagation(); 2042 $modalStack.dismiss(modal.key, 'backdrop click'); 2043 } 2044 }; 2045 2046 // This property is only added to the scope for the purpose of detecting when this directive is rendered. 2047 // We can detect that by using this property in the template associated with this directive and then use 2048 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. 2049 scope.$isRendered = true; 2050 2051 // Deferred object that will be resolved when this modal is render. 2052 var modalRenderDeferObj = $q.defer(); 2053 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. 2054 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. 2055 attrs.$observe('modalRender', function (value) { 2056 if (value == 'true') { 2057 modalRenderDeferObj.resolve(); 2058 } 2059 }); 2060 2061 modalRenderDeferObj.promise.then(function () { 2062 // trigger CSS transitions 2063 scope.animate = true; 2064 2065 var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]'); 2066 /** 2067 * Auto-focusing of a freshly-opened modal element causes any child elements 2068 * with the autofocus attribute to lose focus. This is an issue on touch 2069 * based devices which will show and then hide the onscreen keyboard. 2070 * Attempts to refocus the autofocus element via JavaScript will not reopen 2071 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus 2072 * the modal element if the modal does not contain an autofocus element. 2073 */ 2074 if (inputsWithAutofocus.length) { 2075 inputsWithAutofocus[0].focus(); 2076 } else { 2077 element[0].focus(); 2078 } 2079 2080 // Notify {@link $modalStack} that modal is rendered. 2081 var modal = $modalStack.getTop(); 2082 if (modal) { 2083 $modalStack.modalRendered(modal.key); 2084 } 2085 }); 2086 } 2087 }; 2088 }]) 2089 2090 .directive('modalAnimationClass', [ 2091 function () { 2092 return {
2093 compile: function (tElement, tAttrs) { 2094 if (tAttrs.modalAnimation) { 2095 tElement.addClass(tAttrs.modalAnimationClass); 2096 } 2097 } 2098 }; 2099 }]) 2100 2101 .directive('modalTransclude', function () { 2102 return { 2103 link: function($scope, $element, $attrs, controller, $transclude) { 2104 $transclude($scope.$parent, function(clone) { 2105 $element.empty(); 2106 $element.append(clone); 2107 }); 2108 } 2109 }; 2110 }) 2111 2112 .factory('$modalStack', ['$animate', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap', 2113 function ($animate, $timeout, $document, $compile, $rootScope, $$stackedMap) { 2114 2115 var OPENED_MODAL_CLASS = 'modal-open'; 2116 2117 var backdropDomEl, backdropScope; 2118 var openedWindows = $$stackedMap.createNew(); 2119 var $modalStack = {}; 2120 2121 function backdropIndex() { 2122 var topBackdropIndex = -1; 2123 var opened = openedWindows.keys(); 2124 for (var i = 0; i < opened.length; i++) { 2125 if (openedWindows.get(opened[i]).value.backdrop) { 2126 topBackdropIndex = i; 2127 } 2128 } 2129 return topBackdropIndex; 2130 } 2131 2132 $rootScope.$watch(backdropIndex, function(newBackdropIndex){ 2133 if (backdropScope) { 2134 backdropScope.index = newBackdropIndex; 2135 } 2136 }); 2137 2138 function removeModalWindow(modalInstance) { 2139 2140 var body = $document.find('body').eq(0); 2141 var modalWindow = openedWindows.get(modalInstance).value; 2142 2143 //clean up the stack 2144 openedWindows.remove(modalInstance); 2145 2146 //remove window DOM element 2147 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { 2148 body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0); 2149 checkRemoveBackdrop(); 2150 }); 2151 } 2152 2153 function checkRemoveBackdrop() { 2154 //remove backdrop if no longer needed 2155 if (backdropDomEl && backdropIndex() == -1) { 2156 var backdropScopeRef = backdropScope; 2157 removeAfterAnimate(backdropDomEl, backdropScope, function () { 2158 backdropScopeRef = null; 2159 }); 2160 backdropDomEl = undefined; 2161 backdropScope = undefined; 2162 } 2163 } 2164 2165 function removeAfterAnimate(domEl, scope, done) { 2166 // Closing animation 2167 scope.animate = false; 2168 2169 if (domEl.attr('modal-animation') && $animate.enabled()) { 2170 // transition out 2171 domEl.one('$animate:close', function closeFn() { 2172 $rootScope.$evalAsync(afterAnimating); 2173 }); 2174 } else { 2175 // Ensure this call is async 2176 $timeout(afterAnimating); 2177 } 2178 2179 function afterAnimating() { 2180 if (afterAnimating.done) { 2181 return; 2182 } 2183 afterAnimating.done = true; 2184 2185 domEl.remove(); 2186 scope.$destroy(); 2187 if (done) { 2188 done(); 2189 } 2190 } 2191 } 2192 2193 $document.bind('keydown', function (evt) { 2194 var modal; 2195 2196 if (evt.which === 27) { 2197 modal = openedWindows.top(); 2198 if (modal && modal.value.keyboard) { 2199 evt.preventDefault(); 2200 $rootScope.$apply(function () { 2201 $modalStack.dismiss(modal.key, 'escape key press'); 2202 }); 2203 } 2204 } 2205 }); 2206 2207 $modalStack.open = function (modalInstance, modal) { 2208 2209 var modalOpener = $document[0].activeElement; 2210 2211 openedWindows.add(modalInstance, { 2212 deferred: modal.deferred, 2213 renderDeferred: modal.renderDeferred, 2214 modalScope: modal.scope, 2215 backdrop: modal.backdrop, 2216 keyboard: modal.keyboard 2217 }); 2218 2219 var body = $document.find('body').eq(0), 2220 currBackdropIndex = backdropIndex(); 2221 2222 if (currBackdropIndex >= 0 && !backdropDomEl) { 2223 backdropScope = $rootScope.$new(true); 2224 backdropScope.index = currBackdropIndex; 2225 var angularBackgroundDomEl = angular.element('<div modal-backdrop="modal-backdrop"></div>'); 2226 angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass); 2227 if (modal.animation) {
2228 angularBackgroundDomEl.attr('modal-animation', 'true'); 2229 } 2230 backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope); 2231 body.append(backdropDomEl); 2232 } 2233 2234 var angularDomEl = angular.element('<div modal-window="modal-window"></div>'); 2235 angularDomEl.attr({ 2236 'template-url': modal.windowTemplateUrl, 2237 'window-class': modal.windowClass, 2238 'size': modal.size, 2239 'index': openedWindows.length() - 1, 2240 'animate': 'animate' 2241 }).html(modal.content); 2242 if (modal.animation) { 2243 angularDomEl.attr('modal-animation', 'true'); 2244 } 2245 2246 var modalDomEl = $compile(angularDomEl)(modal.scope); 2247 openedWindows.top().value.modalDomEl = modalDomEl; 2248 openedWindows.top().value.modalOpener = modalOpener; 2249 body.append(modalDomEl); 2250 body.addClass(OPENED_MODAL_CLASS); 2251 }; 2252 2253 function broadcastClosing(modalWindow, resultOrReason, closing) { 2254 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; 2255 } 2256 2257 $modalStack.close = function (modalInstance, result) { 2258 var modalWindow = openedWindows.get(modalInstance); 2259 if (modalWindow && broadcastClosing(modalWindow, result, true)) { 2260 modalWindow.value.deferred.resolve(result); 2261 removeModalWindow(modalInstance); 2262 modalWindow.value.modalOpener.focus(); 2263 return true; 2264 } 2265 return !modalWindow; 2266 }; 2267 2268 $modalStack.dismiss = function (modalInstance, reason) { 2269 var modalWindow = openedWindows.get(modalInstance); 2270 if (modalWindow && broadcastClosing(modalWindow, reason, false)) { 2271 modalWindow.value.deferred.reject(reason); 2272 removeModalWindow(modalInstance); 2273 modalWindow.value.modalOpener.focus(); 2274 return true; 2275 } 2276 return !modalWindow; 2277 }; 2278 2279 $modalStack.dismissAll = function (reason) { 2280 var topModal = this.getTop(); 2281 while (topModal && this.dismiss(topModal.key, reason)) { 2282 topModal = this.getTop(); 2283 } 2284 }; 2285 2286 $modalStack.getTop = function () { 2287 return openedWindows.top(); 2288 }; 2289 2290 $modalStack.modalRendered = function (modalInstance) { 2291 var modalWindow = openedWindows.get(modalInstance); 2292 if (modalWindow) { 2293 modalWindow.value.renderDeferred.resolve(); 2294 } 2295 }; 2296 2297 return $modalStack; 2298 }]) 2299 2300 .provider('$modal', function () { 2301 2302 var $modalProvider = { 2303 options: { 2304 animation: true, 2305 backdrop: true, //can also be false or 'static' 2306 keyboard: true 2307 }, 2308 $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$modalStack', 2309 function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack) { 2310 2311 var $modal = {}; 2312 2313 function getTemplatePromise(options) { 2314 return options.template ? $q.when(options.template) : 2315 $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl); 2316 } 2317 2318 function getResolvePromises(resolves) { 2319 var promisesArr = []; 2320 angular.forEach(resolves, function (value) { 2321 if (angular.isFunction(value) || angular.isArray(value)) { 2322 promisesArr.push($q.when($injector.invoke(value))); 2323 } 2324 }); 2325 return promisesArr; 2326 } 2327 2328 $modal.open = function (modalOptions) { 2329 2330 var modalResultDeferred = $q.defer(); 2331 var modalOpenedDeferred = $q.defer(); 2332 var modalRenderDeferred = $q.defer(); 2333 2334 //prepare an instance of a modal to be injected into controllers and returned to a caller 2335 var modalInstance = { 2336 result: modalResultDeferred.promise, 2337 opened: modalOpenedDeferred.promise, 2338 rendered: modalRenderDeferred.promise, 2339 close: function (result) { 2340 return $modalStack.close(modalInstance, result); 2341 },
2342 dismiss: function (reason) { 2343 return $modalStack.dismiss(modalInstance, reason); 2344 } 2345 }; 2346 2347 //merge and clean up options 2348 modalOptions = angular.extend({}, $modalProvider.options, modalOptions); 2349 modalOptions.resolve = modalOptions.resolve || {}; 2350 2351 //verify options 2352 if (!modalOptions.template && !modalOptions.templateUrl) { 2353 throw new Error('One of template or templateUrl options is required.'); 2354 } 2355 2356 var templateAndResolvePromise = 2357 $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve))); 2358 2359 2360 templateAndResolvePromise.then(function resolveSuccess(tplAndVars) { 2361 2362 var modalScope = (modalOptions.scope || $rootScope).$new(); 2363 modalScope.$close = modalInstance.close; 2364 modalScope.$dismiss = modalInstance.dismiss; 2365 2366 var ctrlInstance, ctrlLocals = {}; 2367 var resolveIter = 1; 2368 2369 //controllers 2370 if (modalOptions.controller) { 2371 ctrlLocals.$scope = modalScope; 2372 ctrlLocals.$modalInstance = modalInstance; 2373 angular.forEach(modalOptions.resolve, function (value, key) { 2374 ctrlLocals[key] = tplAndVars[resolveIter++]; 2375 }); 2376 2377 ctrlInstance = $controller(modalOptions.controller, ctrlLocals); 2378 if (modalOptions.controllerAs) { 2379 modalScope[modalOptions.controllerAs] = ctrlInstance; 2380 } 2381 } 2382 2383 $modalStack.open(modalInstance, { 2384 scope: modalScope, 2385 deferred: modalResultDeferred, 2386 renderDeferred: modalRenderDeferred, 2387 content: tplAndVars[0], 2388 animation: modalOptions.animation, 2389 backdrop: modalOptions.backdrop, 2390 keyboard: modalOptions.keyboard, 2391 backdropClass: modalOptions.backdropClass, 2392 windowClass: modalOptions.windowClass, 2393 windowTemplateUrl: modalOptions.windowTemplateUrl, 2394 size: modalOptions.size 2395 }); 2396 2397 }, function resolveError(reason) { 2398 modalResultDeferred.reject(reason); 2399 }); 2400 2401 templateAndResolvePromise.then(function () { 2402 modalOpenedDeferred.resolve(true); 2403 }, function (reason) { 2404 modalOpenedDeferred.reject(reason); 2405 }); 2406 2407 return modalInstance; 2408 }; 2409 2410 return $modal; 2411 }] 2412 }; 2413 2414 return $modalProvider; 2415 }); 2416 2417angular.module('ui.bootstrap.pagination', []) 2418 2419.controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) { 2420 var self = this, 2421 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl 2422 setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; 2423 2424 this.init = function(ngModelCtrl_, config) { 2425 ngModelCtrl = ngModelCtrl_; 2426 this.config = config; 2427 2428 ngModelCtrl.$render = function() { 2429 self.render(); 2430 }; 2431 2432 if ($attrs.itemsPerPage) { 2433 $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) { 2434 self.itemsPerPage = parseInt(value, 10); 2435 $scope.totalPages = self.calculateTotalPages(); 2436 }); 2437 } else { 2438 this.itemsPerPage = config.itemsPerPage; 2439 } 2440 2441 $scope.$watch('totalItems', function() { 2442 $scope.totalPages = self.calculateTotalPages(); 2443 }); 2444 2445 $scope.$watch('totalPages', function(value) { 2446 setNumPages($scope.$parent, value); // Readonly variable 2447 2448 if ( $scope.page > value ) { 2449 $scope.selectPage(value); 2450 } else { 2451 ngModelCtrl.$render(); 2452 } 2453 }); 2454 }; 2455 2456 this.calculateTotalPages = function() { 2457 var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage); 2458 return Math.max(totalPages || 0, 1); 2459 }; 2460 2461 this.render = function() { 2462 $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1; 2463 }; 2464 2465 $scope.selectPage = function(page, evt) { 2466 if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) { 2467 if (evt && evt.target) { 2468 evt.target.blur(); 2469 } 2470 ngModelCtrl.$setViewValue(page); 2471 ngModelCtrl.$render(); 2472 } 2473 }; 2474 2475 $scope.getText = function( key ) { 2476 return $scope[key + 'Text'] || self.config[key + 'Text']; 2477 }; 2478 $scope.noPrevious = function() { 2479 return $scope.page === 1; 2480 }; 2481 $scope.noNext = function() { 2482 return $scope.page === $scope.totalPages; 2483 }; 2484}]) 2485 2486.constant('paginationConfig', { 2487 itemsPerPage: 10, 2488 boundaryLinks: false, 2489 directionLinks: true, 2490 firstText: 'First', 2491 previousText: 'Previous', 2492 nextText: 'Next', 2493 lastText: 'Last', 2494 rotate: true 2495}) 2496 2497.directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) { 2498 return { 2499 restrict: 'EA', 2500 scope: { 2501 totalItems: '=', 2502 firstText: '@', 2503 previousText: '@', 2504 nextText: '@', 2505 lastText: '@' 2506 }, 2507 require: ['pagination', '?ngModel'], 2508 controller: 'PaginationController', 2509 templateUrl: 'template/pagination/pagination.html', 2510 replace: true, 2511 link: function(scope, element, attrs, ctrls) { 2512 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 2513 2514 if (!ngModelCtrl) { 2515 return; // do nothing if no ng-model 2516 } 2517 2518 // Setup configuration parameters 2519 var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize, 2520 rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate; 2521 scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boun
2521daryLinks; 2522 scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks; 2523 2524 paginationCtrl.init(ngModelCtrl, paginationConfig); 2525 2526 if (attrs.maxSize) { 2527 scope.$parent.$watch($parse(attrs.maxSize), function(value) { 2528 maxSize = parseInt(value, 10); 2529 paginationCtrl.render(); 2530 }); 2531 } 2532 2533 // Create page object used in template 2534 function makePage(number, text, isActive) { 2535 return { 2536 number: number, 2537 text: text, 2538 active: isActive 2539 }; 2540 } 2541 2542 function getPages(currentPage, totalPages) { 2543 var pages = []; 2544 2545 // Default page limits 2546 var startPage = 1, endPage = totalPages; 2547 var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages ); 2548 2549 // recompute if maxSize 2550 if ( isMaxSized ) { 2551 if ( rotate ) { 2552 // Current page is displayed in the middle of the visible ones 2553 startPage = Math.max(currentPage - Math.floor(maxSize/2), 1); 2554 endPage = startPage + maxSize - 1; 2555 2556 // Adjust if limit is exceeded 2557 if (endPage > totalPages) { 2558 endPage = totalPages; 2559 startPage = endPage - maxSize + 1; 2560 } 2561 } else { 2562 // Visible pages are paginated with maxSize 2563 startPage = ((Math.ceil(currentPage / maxSize) - 1) * maxSize) + 1; 2564 2565 // Adjust last page if limit is exceeded 2566 endPage = Math.min(startPage + maxSize - 1, totalPages); 2567 } 2568 } 2569 2570 // Add page number links 2571 for (var number = startPage; number <= endPage; number++) { 2572 var page = makePage(number, number, number === currentPage); 2573 pages.push(page); 2574 } 2575 2576 // Add links to move between page sets 2577 if ( isMaxSized && ! rotate ) { 2578 if ( startPage > 1 ) { 2579 var previousPageSet = makePage(startPage - 1, '...', false); 2580 pages.unshift(previousPageSet); 2581 } 2582 2583 if ( endPage < totalPages ) { 2584 var nextPageSet = makePage(endPage + 1, '...', false); 2585 pages.push(nextPageSet); 2586 } 2587 } 2588 2589 return pages; 2590 } 2591 2592 var originalRender = paginationCtrl.render; 2593 paginationCtrl.render = function() { 2594 originalRender(); 2595 if (scope.page > 0 && scope.page <= scope.totalPages) { 2596 scope.pages = getPages(scope.page, scope.totalPages); 2597 } 2598 }; 2599 } 2600 }; 2601}]) 2602 2603.constant('pagerConfig', { 2604 itemsPerPage: 10, 2605 previousText: '« Previous', 2606 nextText: 'Next »', 2607 align: true 2608}) 2609 2610.directive('pager', ['pagerConfig', function(pagerConfig) { 2611 return { 2612 restrict: 'EA', 2613 scope: { 2614 totalItems: '=', 2615 previousText: '@', 2616 nextText: '@' 2617 }, 2618 require: ['pager', '?ngModel'], 2619 controller: 'PaginationController', 2620 templateUrl: 'template/pagination/pager.html', 2621 replace: true, 2622 link: function(scope, element, attrs, ctrls) { 2623 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 2624 2625 if (!ngModelCtrl) { 2626 return; // do nothing if no ng-model 2627 } 2628 2629 scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align; 2630 paginationCtrl.init(ngModelCtrl, pagerConfig); 2631 } 2632 }; 2633}]); 2634 2635/** 2636 * The following features are still outstanding: animation as a 2637 * function, placement as a function, inside, support for more triggers than 2638 * just mouse enter/leave, html tooltips, and selector delegation. 2639 */ 2640angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] ) 2641 2642/** 2643 * The $tooltip service creates tooltip- and popover-like directives as well as 2644 * houses global options for them. 2645 */ 2646.provider( '$tooltip', function () { 2647 // The default options tooltip and popover. 2648 var defaultOptions = { 2649 placement: 'top', 2650 animation: true, 2651 popupDelay: 0, 2652 useContentExp: false 2653 }; 2654 2655 // Default hide triggers for each show trigger 2656 var triggerMap = { 2657 'mouseenter': 'mouseleave', 2658 'click': 'click',
2659 'focus': 'blur' 2660 }; 2661 2662 // The options specified to the provider globally. 2663 var globalOptions = {}; 2664 2665 /** 2666 * `options({})` allows global configuration of all tooltips in the 2667 * application. 2668 * 2669 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { 2670 * // place tooltips left instead of top by default 2671 * $tooltipProvider.options( { placement: 'left' } ); 2672 * }); 2673 */ 2674 this.options = function( value ) { 2675 angular.extend( globalOptions, value ); 2676 }; 2677 2678 /** 2679 * This allows you to extend the set of trigger mappings available. E.g.: 2680 * 2681 * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' ); 2682 */ 2683 this.setTriggers = function setTriggers ( triggers ) { 2684 angular.extend( triggerMap, triggers ); 2685 }; 2686 2687 /** 2688 * This is a helper function for translating camel-case to snake-case. 2689 */ 2690 function snake_case(name){ 2691 var regexp = /[A-Z]/g; 2692 var separator = '-'; 2693 return name.replace(regexp, function(letter, pos) { 2694 return (pos ? separator : '') + letter.toLowerCase(); 2695 }); 2696 } 2697 2698 /** 2699 * Returns the actual instance of the $tooltip service. 2700 * TODO support multiple triggers 2701 */ 2702 this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) { 2703 return function $tooltip ( type, prefix, defaultTriggerShow, options ) { 2704 options = angular.extend( {}, defaultOptions, globalOptions, options ); 2705 2706 /** 2707 * Returns an object of show and hide triggers. 2708 * 2709 * If a trigger is supplied, 2710 * it is used to show the tooltip; otherwise, it will use the `trigger` 2711 * option passed to the `$tooltipProvider.options` method; else it will 2712 * default to the trigger supplied to this directive factory. 2713 * 2714 * The hide trigger is based on the show trigger. If the `trigger` option 2715 * was passed to the `$tooltipProvider.options` method, it will use the 2716 * mapped trigger from `triggerMap` or the passed trigger if the map is 2717 * undefined; otherwise, it uses the `triggerMap` value of the show 2718 * trigger; else it will just use the show trigger. 2719 */ 2720 function getTriggers ( trigger ) { 2721 var show = trigger || options.trigger || defaultTriggerShow; 2722 var hide = triggerMap[show] || show; 2723 return { 2724 show: show, 2725 hide: hide 2726 }; 2727 } 2728 2729 var directiveName = snake_case( type ); 2730 2731 var startSym = $interpolate.startSymbol(); 2732 var endSym = $interpolate.endSymbol(); 2733 var template = 2734 '<div '+ directiveName +'-popup '+ 2735 'title="'+startSym+'title'+endSym+'" '+ 2736 (options.useContentExp ? 2737 'content-exp="contentExp()" ' : 2738 'content="'+startSym+'content'+endSym+'" ') + 2739 'placement="'+startSym+'placement'+endSym+'" '+ 2740 'popup-class="'+startSym+'popupClass'+endSym+'" '+ 2741 'animation="animation" '+ 2742 'is-open="isOpen"'+ 2743 'origin-scope="origScope" '+ 2744 '>'+ 2745 '</div>'; 2746 2747 return { 2748 restrict: 'EA', 2749 compile: function (tElem, tAttrs) { 2750 var tooltipLinker = $compile( template ); 2751 2752 return function link ( scope, element, attrs, tooltipCtrl ) { 2753 var tooltip; 2754 var tooltipLinkedScope; 2755 var transitionTimeout; 2756 var popupTimeout; 2757 var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false; 2758 var triggers = getTriggers( undefined ); 2759 var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']); 2760 var ttScope = scope.$new(true); 2761 2762 var positionTooltip = function () { 2763 if (!tooltip) { return; } 2764 2765 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); 2766 ttPosition.top += 'px'; 2767 ttPosition.left += 'px'; 2768 2769 // Now set the calculated positioning. 2770 tooltip.css( ttPosition ); 2771 }; 2772 2773 // Set up the correct scope to allow transclusion later 2774 ttScope.origScope = scope; 2775 2776 // By default, the tooltip is not open. 2777 // TODO add ability to start tooltip opened 2778 ttScope.isOpen = false;
2779 2780 function toggleTooltipBind () { 2781 if ( ! ttScope.isOpen ) { 2782 showTooltipBind(); 2783 } else { 2784 hideTooltipBind(); 2785 } 2786 } 2787 2788 // Show the tooltip with delay if specified, otherwise show it immediately 2789 function showTooltipBind() { 2790 if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) { 2791 return; 2792 } 2793 2794 prepareTooltip(); 2795 2796 if ( ttScope.popupDelay ) { 2797 // Do nothing if the tooltip was already scheduled to pop-up. 2798 // This happens if show is triggered multiple times before any hide is triggered. 2799 if (!popupTimeout) { 2800 popupTimeout = $timeout( show, ttScope.popupDelay, false ); 2801 popupTimeout.then(function(reposition){reposition();}); 2802 } 2803 } else { 2804 show()(); 2805 } 2806 } 2807 2808 function hideTooltipBind () { 2809 scope.$apply(function () { 2810 hide(); 2811 }); 2812 } 2813 2814 // Show the tooltip popup element. 2815 function show() { 2816 2817 popupTimeout = null; 2818 2819 // If there is a pending remove transition, we must cancel it, lest the 2820 // tooltip be mysteriously removed. 2821 if ( transitionTimeout ) { 2822 $timeout.cancel( transitionTimeout ); 2823 transitionTimeout = null; 2824 } 2825 2826 // Don't show empty tooltips. 2827 if ( !(options.useContentExp ? ttScope.contentExp() : ttScope.content) ) { 2828 return angular.noop; 2829 } 2830 2831 createTooltip(); 2832 2833 // Set the initial positioning. 2834 tooltip.css({ top: 0, left: 0, display: 'block' }); 2835 ttScope.$digest(); 2836 2837 positionTooltip(); 2838 2839 // And show the tooltip. 2840 ttScope.isOpen = true; 2841 ttScope.$apply(); // digest required as $apply is not called 2842 2843 // Return positioning function as promise callback for correct 2844 // positioning after draw. 2845 return positionTooltip; 2846 } 2847 2848 // Hide the tooltip popup element. 2849 function hide() { 2850 // First things first: we don't show it anymore. 2851 ttScope.isOpen = false; 2852 2853 //if tooltip is going to be shown after delay, we must cancel this 2854 $timeout.cancel( popupTimeout ); 2855 popupTimeout = null; 2856 2857 // And now we remove it from the DOM. However, if we have animation, we 2858 // need to wait for it to expire beforehand. 2859 // FIXME: this is a placeholder for a port of the transitions library. 2860 if ( ttScope.animation ) { 2861 if (!transitionTimeout) { 2862 transitionTimeout = $timeout(removeTooltip, 500); 2863 } 2864 } else { 2865 removeTooltip(); 2866 } 2867 } 2868 2869 function createTooltip() { 2870 // There can only be one tooltip element per directive shown at once. 2871 if (tooltip) { 2872 removeTooltip(); 2873 } 2874 tooltipLinkedScope = ttScope.$new(); 2875 tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) { 2876 if ( appendToBody ) { 2877 $document.find( 'body' ).append( tooltip ); 2878 } else { 2879 element.after( tooltip ); 2880 } 2881 }); 2882 2883 tooltipLinkedScope.$watch(function () { 2884 $timeout(positionTooltip, 0, false); 2885 }); 2886 2887 if (options.useContentExp) { 2888 tooltipLinkedScope.$watch('contentExp()', function (val) { 2889 if (!val && ttScope.isOpen ) { 2890 hide(); 2891 } 2892 }); 2893 } 2894 } 2895 2896 function removeTooltip() { 2897 transitionTimeout = null; 2898 if (tooltip) { 2899 tooltip.remove(); 2900 tooltip = null; 2901 } 2902 if (tooltipLinkedScope) { 2903 tooltipLinkedScope.$destroy(); 2904 tooltipLinkedScope = null; 2905 } 2906 } 2907 2908 function prepareTooltip() { 2909 prepPopupClass(); 2910 prepPlacement(); 2911 prepPopupDelay(); 2912 } 2913 2914 ttScope.contentExp = function () { 2915 return scope.$eval(attrs[type]); 2916 }; 2917 2918 /** 2919 * Observe the relevant attributes. 2920 */ 2921 if (!options.useContentExp) { 2922 attrs.$observe( type, function ( val ) { 2923 ttScope.content = val; 2924 2925 if (!val && ttScope.isOpen ) { 2926 hide(); 2927 } 2928 }); 2929 } 2930 2931 attrs.$observe( 'disabled', function ( val ) { 2932 if (val && ttScope.isOpen ) { 2933 hide(); 2934 } 2935 }); 2936 2937 attrs.$observe( prefix+'Title', function ( val ) { 2938 ttScope.title = val; 2939 }); 2940 2941 function prepPopupClass() { 2942 ttScope.popupClass = attrs[prefix + 'Class']; 2943 } 2944 2945 function prepPlacement() { 2946 var val = attrs[ prefix + 'Placement' ]; 2947 ttScope.placement = angular.isDefined( val ) ? val : options.placement; 2948 } 2949 2950 function prepPopupDelay() { 2951 var val = attrs[ prefix + 'PopupDelay' ];
2952 var delay = parseInt( val, 10 ); 2953 ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay; 2954 } 2955 2956 var unregisterTriggers = function () { 2957 element.unbind(triggers.show, showTooltipBind); 2958 element.unbind(triggers.hide, hideTooltipBind); 2959 }; 2960 2961 function prepTriggers() { 2962 var val = attrs[ prefix + 'Trigger' ]; 2963 unregisterTriggers(); 2964 2965 triggers = getTriggers( val ); 2966 2967 if ( triggers.show === triggers.hide ) { 2968 element.bind( triggers.show, toggleTooltipBind ); 2969 } else { 2970 element.bind( triggers.show, showTooltipBind ); 2971 element.bind( triggers.hide, hideTooltipBind ); 2972 } 2973 } 2974 prepTriggers(); 2975 2976 var animation = scope.$eval(attrs[prefix + 'Animation']); 2977 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; 2978 2979 var appendToBodyVal = scope.$eval(attrs[prefix + 'AppendToBody']); 2980 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; 2981 2982 // if a tooltip is attached to <body> we need to remove it on 2983 // location change as its parent scope will probably not be destroyed 2984 // by the change. 2985 if ( appendToBody ) { 2986 scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () { 2987 if ( ttScope.isOpen ) { 2988 hide(); 2989 } 2990 }); 2991 } 2992 2993 // Make sure tooltip is destroyed and removed. 2994 scope.$on('$destroy', function onDestroyTooltip() { 2995 $timeout.cancel( transitionTimeout ); 2996 $timeout.cancel( popupTimeout ); 2997 unregisterTriggers(); 2998 removeTooltip(); 2999 ttScope = null; 3000 }); 3001 }; 3002 } 3003 }; 3004 }; 3005 }]; 3006}) 3007 3008// This is mostly ngInclude code but with a custom scope 3009.directive( 'tooltipTemplateTransclude', [ 3010 '$animate', '$sce', '$compile', '$templateRequest', 3011function ($animate , $sce , $compile , $templateRequest) { 3012 return { 3013 link: function ( scope, elem, attrs ) { 3014 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); 3015 3016 var changeCounter = 0, 3017 currentScope, 3018 previousElement, 3019 currentElement; 3020 3021 var cleanupLastIncludeContent = function() { 3022 if (previousElement) { 3023 previousElement.remove(); 3024 previousElement = null; 3025 } 3026 if (currentScope) { 3027 currentScope.$destroy(); 3028 currentScope = null; 3029 } 3030 if (currentElement) { 3031 $animate.leave(currentElement).then(function() { 3032 previousElement = null; 3033 }); 3034 previousElement = currentElement; 3035 currentElement = null; 3036 } 3037 }; 3038 3039 scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function (src) { 3040 var thisChangeId = ++changeCounter; 3041 3042 if (src) { 3043 //set the 2nd param to true to ignore the template request error so that the inner 3044 //contents and scope can be cleaned up. 3045 $templateRequest(src, true).then(function(response) { 3046 if (thisChangeId !== changeCounter) { return; } 3047 var newScope = origScope.$new(); 3048 var template = response; 3049 3050 var clone = $compile(template)(newScope, function(clone) { 3051 cleanupLastIncludeContent(); 3052 $animate.enter(clone, elem); 3053 }); 3054 3055 currentScope = newScope; 3056 currentElement = clone; 3057 3058 currentScope.$emit('$includeContentLoaded', src); 3059 }, function() { 3060 if (thisChangeId === changeCounter) { 3061 cleanupLastIncludeContent(); 3062 scope.$emit('$includeContentError', src); 3063 } 3064 }); 3065 scope.$emit('$includeContentRequested', src); 3066 } else { 3067 cleanupLastIncludeContent(); 3068 } 3069 }); 3070 3071 scope.$on('$destroy', cleanupLastIncludeContent); 3072 } 3073 }; 3074}]) 3075 3076/** 3077 * Note that it's intentional that these classes are *not* applied through $animate. 3078 * They must not be animated as they're expected to be present on the tooltip on 3079 * initialization. 3080 */ 3081.directive('tooltipClasses', function () { 3082 return { 3083 restrict: 'A', 3084 link: function (scope, element, attrs) { 3085 if (scope.placement) { 3086 element.addClass(scope.placement); 3087 } 3088 if (scope.popupClass) { 3089 element.addClass(scope.popupClass); 3090 } 3091 if (scope.animation()) { 3092 element.addClass(attrs.tooltipAnimationClass); 3093 } 3094 } 3095 }; 3096}) 3097 3098.directive( 'tooltipPopup', function () { 3099 return { 3100 restrict: 'EA', 3101 replace: true, 3102 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 3103 templateUrl: 'template/tooltip/tooltip-popup.html' 3104 }; 3105}) 3106 3107.directive( 'tooltip', [ '$tooltip', function ( $tooltip ) { 3108 return $tooltip( 'tooltip', 'tooltip', 'mouseenter' ); 3109}]) 3110 3111.directive( 'tooltipTemplatePopup', function () { 3112 return { 3113 restrict: 'EA', 3114 replace: true, 3115 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', 3116 originScope: '&' }, 3117 templateUrl: 'template/tooltip/tooltip-template-popup.html' 3118 }; 3119}) 3120 3121.directive( 'tooltipTemplate', [ '$tooltip', function ( $tooltip ) { 3122 return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', { 3123 useContentExp: true 3124 }); 3125}]) 3126 3127.directive( 'tooltipHtmlPopup', function () { 3128 return { 3129 restrict: 'EA', 3130 replace: true, 3131 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 3132 templateUrl: 'template/tooltip/tooltip-html-popup.html' 3133 }; 3134}) 3135 3136.directive( 'tooltipHtml', [ '$tooltip', function ( $tooltip ) { 3137 return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', { 3138 useContentExp: true 3139 }); 3140}]) 3141 3142/* 3143Deprecated 3144*/ 3145.directive( 'tooltipHtmlUnsafePopup', function () { 3146 return { 3147 restrict: 'EA', 3148 replace: true, 3149 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 3150 templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html' 3151 }; 3152}) 3153 3154.value('tooltipHtmlUnsafeSuppressDeprecated', false) 3155.directive( 'tooltipHtmlUnsafe', [ 3156 '$tooltip', 'tooltipHtmlUnsafeSuppressDeprecated', '$log', 3157function ( $tooltip , tooltipHtmlUnsafeSuppressDeprecated , $log) { 3158 if (!tooltipHtmlUnsafeSuppressDeprecated) { 3159 $log.warn('tooltip-html-unsafe is now deprecated. Use tooltip-html or tooltip-template instead.'); 3160 } 3161 return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' ); 3162}]); 3163 3164/** 3165 * The following features are still outstanding: popup delay, animation as a 3166 * function, placement as a function, inside, support for more triggers than 3167 * just mouse enter/leave, html popovers, and selector delegatation. 3168 */ 3169angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] ) 3170 3171.directive( 'popoverTemplatePopup', function () { 3172 return { 3173 restrict: 'EA', 3174 replace: true, 3175 scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', 3176 originScope: '&' }, 3177 templateUrl: 'template/popover/popover-template.html' 3178 }; 3179}) 3180 3181.directive( 'popoverTemplate', [ '$tooltip', function ( $tooltip ) { 3182 return $tooltip( 'popoverTemplate', 'popover', 'click', { 3183 useContentExp: true 3184 } ); 3185}]) 3186 3187.directive( 'popoverPopup', function () { 3188 return { 3189 restrict: 'EA', 3190 replace: true, 3191 scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 3192 templateUrl: 'template/popover/popover.html' 3193 }; 3194}) 3195 3196.directive( 'popover', [ '$tooltip', function ( $tooltip ) { 3197 return $tooltip( 'popover', 'popover', 'click' ); 3198}]); 3199 3200angular.module('ui.bootstrap.progressbar', []) 3201 3202.constant('progressConfig', { 3203 animate: true, 3204 max: 100 3205}) 3206 3207.controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) { 3208 var self = this, 3209 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; 3210 3211 this.bars = []; 3212 $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max; 3213 3214 this.addBar = function(bar, element) { 3215 if ( !animate ) { 3216 element.css({'transition': 'none'}); 3217 } 3218 3219 this.bars.push(bar); 3220 3221 bar.$watch('value', function( value ) { 3222 bar.percent = +(100 * value / $scope.max).toFixed(2); 3223 }); 3224 3225 bar.$on('$destroy', function() { 3226 element = null; 3227 self.removeBar(bar); 3228 }); 3229 }; 3230 3231 this.removeBar = function(bar) { 3232 this.bars.splice(this.bars.indexOf(bar), 1); 3233 }; 3234}]) 3235 3236.directive('progress', function() { 3237 return { 3238 restrict: 'EA', 3239 replace: true, 3240 transclude: true, 3241 controller: 'ProgressController', 3242 require: 'progress', 3243 scope: {}, 3244 templateUrl: 'template/progressbar/progress.html' 3245 }; 3246}) 3247 3248.directive('bar', function() { 3249 return { 3250 restrict: 'EA', 3251 replace: true, 3252 transclude: true, 3253 require: '^progress', 3254 scope: { 3255 value: '=', 3256 max: '=?', 3257 type: '@' 3258 }, 3259 templateUrl: 'template/progressbar/bar.html', 3260 link: function(scope, element, attrs, progressCtrl) { 3261 progressCtrl.addBar(scope, element); 3262 } 3263 }; 3264}) 3265 3266.directive('progressbar', function() { 3267 return { 3268 restrict: 'EA', 3269 replace: true, 3270 transclude: true, 3271 controller: 'ProgressController', 3272 scope: { 3273 value: '=', 3274 max: '=?', 3275 type: '@' 3276 }, 3277 templateUrl: 'template/progressbar/progressbar.html', 3278 link: function(scope, element, attrs, progressCtrl) { 3279 progressCtrl.addBar(scope, angular.element(element.children()[0])); 3280 } 3281 }; 3282}); 3283 3284angular.module('ui.bootstrap.rating', []) 3285 3286.constant('ratingConfig', { 3287 max: 5, 3288 stateOn: null, 3289 stateOff: null 3290}) 3291 3292.controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) { 3293 var ngModelCtrl = { $setViewValue: angular.noop }; 3294 3295 this.init = function(ngModelCtrl_) { 3296 ngModelCtrl = ngModelCtrl_; 3297 ngModelCtrl.$render = this.render; 3298 3299 ngModelCtrl.$formatters.push(function(value) { 3300 if (angular.isNumber(value) && value << 0 !== value) { 3301 value = Math.round(value); 3302 } 3303 return value; 3304 }); 3305 3306 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; 3307 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; 3308 3309 var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) : 3310 new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max );
3311 $scope.range = this.buildTemplateObjects(ratingStates); 3312 }; 3313 3314 this.buildTemplateObjects = function(states) { 3315 for (var i = 0, n = states.length; i < n; i++) { 3316 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]); 3317 } 3318 return states; 3319 }; 3320 3321 $scope.rate = function(value) { 3322 if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) { 3323 ngModelCtrl.$setViewValue(value); 3324 ngModelCtrl.$render(); 3325 } 3326 }; 3327 3328 $scope.enter = function(value) { 3329 if ( !$scope.readonly ) { 3330 $scope.value = value; 3331 } 3332 $scope.onHover({value: value}); 3333 }; 3334 3335 $scope.reset = function() { 3336 $scope.value = ngModelCtrl.$viewValue; 3337 $scope.onLeave(); 3338 }; 3339 3340 $scope.onKeydown = function(evt) { 3341 if (/(37|38|39|40)/.test(evt.which)) { 3342 evt.preventDefault(); 3343 evt.stopPropagation(); 3344 $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) ); 3345 } 3346 }; 3347 3348 this.render = function() { 3349 $scope.value = ngModelCtrl.$viewValue; 3350 }; 3351}]) 3352 3353.directive('rating', function() { 3354 return { 3355 restrict: 'EA', 3356 require: ['rating', 'ngModel'], 3357 scope: { 3358 readonly: '=?', 3359 onHover: '&', 3360 onLeave: '&' 3361 }, 3362 controller: 'RatingController', 3363 templateUrl: 'template/rating/rating.html', 3364 replace: true, 3365 link: function(scope, element, attrs, ctrls) { 3366 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 3367 ratingCtrl.init( ngModelCtrl ); 3368 } 3369 }; 3370}); 3371 3372/** 3373 * @ngdoc overview 3374 * @name ui.bootstrap.tabs 3375 * 3376 * @description 3377 * AngularJS version of the tabs directive. 3378 */ 3379 3380angular.module('ui.bootstrap.tabs', []) 3381 3382.controller('TabsetController', ['$scope', function TabsetCtrl($scope) { 3383 var ctrl = this, 3384 tabs = ctrl.tabs = $scope.tabs = []; 3385 3386 ctrl.select = function(selectedTab) { 3387 angular.forEach(tabs, function(tab) { 3388 if (tab.active && tab !== selectedTab) { 3389 tab.active = false; 3390 tab.onDeselect(); 3391 } 3392 }); 3393 selectedTab.active = true; 3394 selectedTab.onSelect(); 3395 }; 3396 3397 ctrl.addTab = function addTab(tab) { 3398 tabs.push(tab); 3399 // we can't run the select function on the first tab 3400 // since that would select it twice 3401 if (tabs.length === 1 && tab.active !== false) { 3402 tab.active = true; 3403 } else if (tab.active) { 3404 ctrl.select(tab); 3405 } 3406 else { 3407 tab.active = false; 3408 } 3409 }; 3410 3411 ctrl.removeTab = function removeTab(tab) { 3412 var index = tabs.indexOf(tab); 3413 //Select a new tab if the tab to be removed is selected and not destroyed 3414 if (tab.active && tabs.length > 1 && !destroyed) { 3415 //If this is the last tab, select the previous tab. else, the next tab. 3416 var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1; 3417 ctrl.select(tabs[newActiveIndex]); 3418 } 3419 tabs.splice(index, 1); 3420 }; 3421 3422 var destroyed; 3423 $scope.$on('$destroy', function() { 3424 destroyed = true; 3425 }); 3426}]) 3427 3428/** 3429 * @ngdoc directive 3430 * @name ui.bootstrap.tabs.directive:tabset 3431 * @restrict EA 3432 * 3433 * @description 3434 * Tabset is the outer container for the tabs directive 3435 * 3436 * @param {boolean=} vertical Whether or not to use vertical styling for the tabs. 3437 * @param {boolean=} justified Whether or not to use justified styling for the tabs. 3438 * 3439 * @example 3440<example module="ui.bootstrap"> 3441 <file name="index.html"> 3442 <tabset> 3443 <tab heading="Tab 1"><b>First</b> Content!</tab> 3444 <tab heading="Tab 2"><i>Second</i> Content!</tab> 3445 </tabset> 3446 <hr /> 3447 <tabset vertical="true"> 3448 <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab> 3449 <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab> 3450 </tabset> 3451 <tabset justified="true"> 3452 <tab heading="Justified Tab 1"><b>First</b> Justified Content!</tab> 3453 <tab heading="Justified Tab 2"><i>Second</i> Justified Content!</tab> 3454 </tabset> 3455 </file> 3456</example> 3457 */ 3458.directive('tabset', function() { 3459 return { 3460 restrict: 'EA', 3461 transclude: true, 3462 replace: true, 3463 scope: { 3464 type: '@' 3465 }, 3466 controller: 'TabsetController', 3467 templateUrl: 'template/tabs/tabset.html', 3468 link: function(scope, element, attrs) { 3469 scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
3470 scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false; 3471 } 3472 }; 3473}) 3474 3475/** 3476 * @ngdoc directive 3477 * @name ui.bootstrap.tabs.directive:tab 3478 * @restrict EA 3479 * 3480 * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}. 3481 * @param {string=} select An expression to evaluate when the tab is selected. 3482 * @param {boolean=} active A binding, telling whether or not this tab is selected. 3483 * @param {boolean=} disabled A binding, telling whether or not this tab is disabled. 3484 * 3485 * @description 3486 * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}. 3487 * 3488 * @example 3489<example module="ui.bootstrap"> 3490 <file name="index.html"> 3491 <div ng-controller="TabsDemoCtrl"> 3492 <button class="btn btn-small" ng-click="items[0].active = true"> 3493 Select item 1, using active binding 3494 </button> 3495 <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled"> 3496 Enable/disable item 2, using disabled binding 3497 </button> 3498 <br /> 3499 <tabset> 3500 <tab heading="Tab 1">First Tab</tab> 3501 <tab select="alertMe()"> 3502 <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading> 3503 Second Tab, with alert callback and html heading! 3504 </tab> 3505 <tab ng-repeat="item in items" 3506 heading="{{item.title}}" 3507 disabled="item.disabled" 3508 active="item.active"> 3509 {{item.content}} 3510 </tab> 3511 </tabset> 3512 </div> 3513 </file> 3514 <file name="script.js"> 3515 function TabsDemoCtrl($scope) { 3516 $scope.items = [ 3517 { title:"Dynamic Title 1", content:"Dynamic Item 0" }, 3518 { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true } 3519 ]; 3520 3521 $scope.alertMe = function() { 3522 setTimeout(function() { 3523 alert("You've selected the alert tab!"); 3524 }); 3525 }; 3526 }; 3527 </file> 3528</example> 3529 */ 3530 3531/** 3532 * @ngdoc directive 3533 * @name ui.bootstrap.tabs.directive:tabHeading 3534 * @restrict EA 3535 * 3536 * @description 3537 * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element. 3538 * 3539 * @example 3540<example module="ui.bootstrap"> 3541 <file name="index.html"> 3542 <tabset> 3543 <tab> 3544 <tab-heading><b>HTML</b> in my titles?!</tab-heading> 3545 And some content, too! 3546 </tab> 3547 <tab> 3548 <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading> 3549 That's right. 3550 </tab> 3551 </tabset> 3552 </file> 3553</example> 3554 */ 3555.directive('tab', ['$parse', '$log', function($parse, $log) { 3556 return { 3557 require: '^tabset', 3558 restrict: 'EA', 3559 replace: true, 3560 templateUrl: 'template/tabs/tab.html', 3561 transclude: true, 3562 scope: { 3563 active: '=?', 3564 heading: '@', 3565 onSelect: '&select', //This callback is called in contentHeadingTransclude 3566 //once it inserts the tab's content into the dom 3567 onDeselect: '&deselect' 3568 }, 3569 controller: function() { 3570 //Empty controller so other directives can require being 'under' a tab 3571 }, 3572 compile: function(elm, attrs, transclude) { 3573 return function postLink(scope, elm, attrs, tabsetCtrl) { 3574 scope.$watch('active', function(active) { 3575 if (active) { 3576 tabsetCtrl.select(scope); 3577 } 3578 }); 3579 3580 scope.disabled = false; 3581 if ( attrs.disable ) { 3582 scope.$parent.$watch($parse(attrs.disable), function(value) { 3583 scope.disabled = !! value; 3584 }); 3585 } 3586 3587 // Deprecation support of "disabled" parameter 3588 // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677 3589 // This code is duplicated from the lines above to make it easy to remove once 3590 // the feature has been completely deprecated 3591 if ( attrs.disabled ) { 3592 $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"'); 3593 scope.$parent.$watch($parse(attrs.disabled), function(value) { 3594 scope.disabled = !! value; 3595 }); 3596 } 3597 3598 scope.select = function() { 3599 if ( !scope.disabled ) { 3600 scope.active = true; 3601 } 3602 }; 3603 3604 tabsetCtrl.addTab(scope); 3605 scope.$on('$destroy', function() { 3606 tabsetCtrl.removeTab(scope); 3607 }); 3608 3609 //We need to transclude later, once the content container is ready. 3610 //when this link happens, we're inside a tab heading. 3611 scope.$transcludeFn = transclude; 3612 }; 3613 } 3614 }; 3615}]) 3616 3617.directive('tabHeadingTransclude', [function() { 3618 return { 3619 restrict: 'A', 3620 require: '^tab', 3621 link: function(scope, elm, attrs, tabCtrl) { 3622 scope.$watch('headingElement', function updateHeadingElement(heading) { 3623 if (heading) { 3624 elm.html(''); 3625 elm.append(heading); 3626 } 3627 }); 3628 } 3629 }; 3630}]) 3631 3632.directive('tabContentTransclude', function() { 3633 return { 3634 restrict: 'A', 3635 require: '^tabset', 3636 link: function(scope, elm, attrs) { 3637 var tab = scope.$eval(attrs.tabContentTransclude); 3638 3639 //Now our tab is ready to be transcluded: both the tab heading area 3640 //and the tab content area are loaded. Transclude 'em both. 3641 tab.$transcludeFn(tab.$parent, function(contents) { 3642 angular.forEach(contents, function(node) { 3643 if (isTabHeading(node)) { 3644 //Let tabHeadingTransclude know. 3645 tab.headingElement = node; 3646 } else { 3647 elm.append(node); 3648 } 3649 }); 3650 }); 3651 } 3652 }; 3653 function isTabHeading(node) { 3654 return node.tagName && ( 3655 node.hasAttribute('tab-heading') || 3656 node.hasAttribute('data-tab-heading') || 3657 node.tagName.toLowerCase() === 'tab-heading' || 3658 node.tagName.toLowerCase() === 'data-tab-heading' 3659 ); 3660 } 3661}) 3662 3663; 3664 3665angular.module('ui.bootstrap.timepicker', []) 3666 3667.constant('timepickerConfig', { 3668 hourStep: 1, 3669 minuteStep: 1, 3670 showMeridian: true, 3671 meridians: null, 3672 readonlyInput: false, 3673 mousewheel: true, 3674 arrowkeys: true 3675}) 3676 3677.controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) { 3678 var selected = new Date(), 3679 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl 3680 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS; 3681 3682 this.init = function( ngModelCtrl_, inputs ) { 3683 ngModelCtrl = ngModelCtrl_; 3684 ngModelCtrl.$render = this.render; 3685 3686 ngModelCtrl.$formatters.unshift(function (modelValue) { 3687 return modelValue ? new Date( modelValue ) : null; 3688 }); 3689 3690 var hoursInputEl = inputs.eq(0), 3691 minutesInputEl = inputs.eq(1); 3692 3693 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; 3694 if ( mousewheel ) { 3695 this.setupMousewheelEvents( hoursInputEl, minutesInputEl ); 3696 } 3697 3698 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; 3699 if (arrowkeys) { 3700 this.setupArrowkeyEvents( hoursInputEl, minutesInputEl ); 3701 } 3702 3703 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; 3704 this.setupInputEvents( hoursInputEl, minutesInputEl ); 3705 }; 3706 3707 var hourStep = timepickerConfig.hourStep; 3708 if ($attrs.hourStep) { 3709 $scope.$parent.$watch($parse($attrs.hourStep), function(value) { 3710 hourStep = parseInt(value, 10); 3711 }); 3712 } 3713 3714 var minuteStep = timepickerConfig.minuteStep; 3715 if ($attrs.minuteStep) { 3716 $scope.$parent.$watch($parse($attrs.minuteStep), function(value) { 3717 minuteStep = parseInt(value, 10); 3718 }); 3719 } 3720 3721 // 12H / 24H mode 3722 $scope.showMeridian = timepickerConfig.showMeridian; 3723 if ($attrs.showMeridian) { 3724 $scope.$parent.$watch($parse($attrs.showMeridian), function(value) { 3725 $scope.showMeridian = !!value; 3726 3727 if ( ngModelCtrl.$error.time ) { 3728 // Evaluate from template 3729 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); 3730 if (angular.isDefined( hours ) && angular.isDefined( minutes )) { 3731 selected.setHours( hours ); 3732 refresh(); 3733 } 3734 } else { 3735 updateTemplate(); 3736 } 3737 }); 3738 } 3739
3740 // Get $scope.hours in 24H mode if valid 3741 function getHoursFromTemplate ( ) { 3742 var hours = parseInt( $scope.hours, 10 ); 3743 var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24); 3744 if ( !valid ) { 3745 return undefined; 3746 } 3747 3748 if ( $scope.showMeridian ) { 3749 if ( hours === 12 ) { 3750 hours = 0; 3751 } 3752 if ( $scope.meridian === meridians[1] ) { 3753 hours = hours + 12; 3754 } 3755 } 3756 return hours; 3757 } 3758 3759 function getMinutesFromTemplate() { 3760 var minutes = parseInt($scope.minutes, 10); 3761 return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined; 3762 } 3763 3764 function pad( value ) { 3765 return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value.toString(); 3766 } 3767 3768 // Respond on mousewheel spin 3769 this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) { 3770 var isScrollingUp = function(e) { 3771 if (e.originalEvent) { 3772 e = e.originalEvent; 3773 } 3774 //pick correct delta variable depending on event 3775 var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY; 3776 return (e.detail || delta > 0); 3777 }; 3778 3779 hoursInputEl.bind('mousewheel wheel', function(e) { 3780 $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() ); 3781 e.preventDefault(); 3782 }); 3783 3784 minutesInputEl.bind('mousewheel wheel', function(e) { 3785 $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() ); 3786 e.preventDefault(); 3787 }); 3788 3789 }; 3790 3791 // Respond on up/down arrowkeys 3792 this.setupArrowkeyEvents = function( hoursInputEl, minutesInputEl ) { 3793 hoursInputEl.bind('keydown', function(e) { 3794 if ( e.which === 38 ) { // up 3795 e.preventDefault(); 3796 $scope.incrementHours(); 3797 $scope.$apply(); 3798 } 3799 else if ( e.which === 40 ) { // down 3800 e.preventDefault(); 3801 $scope.decrementHours(); 3802 $scope.$apply(); 3803 } 3804 }); 3805 3806 minutesInputEl.bind('keydown', function(e) { 3807 if ( e.which === 38 ) { // up 3808 e.preventDefault(); 3809 $scope.incrementMinutes(); 3810 $scope.$apply(); 3811 } 3812 else if ( e.which === 40 ) { // down 3813 e.preventDefault(); 3814 $scope.decrementMinutes(); 3815 $scope.$apply(); 3816 } 3817 }); 3818 }; 3819 3820 this.setupInputEvents = function( hoursInputEl, minutesInputEl ) { 3821 if ( $scope.readonlyInput ) { 3822 $scope.updateHours = angular.noop; 3823 $scope.updateMinutes = angular.noop; 3824 return; 3825 } 3826 3827 var invalidate = function(invalidHours, invalidMinutes) { 3828 ngModelCtrl.$setViewValue( null ); 3829 ngModelCtrl.$setValidity('time', false); 3830 if (angular.isDefined(invalidHours)) { 3831 $scope.invalidHours = invalidHours; 3832 } 3833 if (angular.isDefined(invalidMinutes)) { 3834 $scope.invalidMinutes = invalidMinutes; 3835 } 3836 }; 3837 3838 $scope.updateHours = function() { 3839 var hours = getHoursFromTemplate(); 3840 3841 if ( angular.isDefined(hours) ) { 3842 selected.setHours( hours ); 3843 refresh( 'h' ); 3844 } else { 3845 invalidate(true); 3846 } 3847 }; 3848 3849 hoursInputEl.bind('blur', function(e) { 3850 if ( !$scope.invalidHours && $scope.hours < 10) { 3851 $scope.$apply( function() { 3852 $scope.hours = pad( $scope.hours ); 3853 }); 3854 } 3855 }); 3856 3857 $scope.updateMinutes = function() { 3858 var minutes = getMinutesFromTemplate(); 3859 3860 if ( angular.isDefined(minutes) ) { 3861 selected.setMinutes( minutes ); 3862 refresh( 'm' ); 3863 } else { 3864 invalidate(undefined, true); 3865 } 3866 }; 3867 3868 minutesInputEl.bind('blur', function(e) { 3869 if ( !$scope.invalidMinutes && $scope.minutes < 10 ) { 3870 $scope.$apply( function() { 3871 $scope.minutes = pad( $scope.minutes ); 3872 }); 3873 } 3874 }); 3875 3876 }; 3877 3878 this.render = function() { 3879 var date = ngModelCtrl.$viewValue; 3880 3881 if ( isNaN(date) ) { 3882 ngModelCtrl.$setValidity('time', false); 3883 $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.'); 3884 } else { 3885 if ( date ) { 3886 selected = date; 3887 } 3888 makeValid(); 3889 updateTemplate(); 3890 } 3891 }; 3892 3893 // Call internally when we know that model is valid. 3894 function refresh( keyboardChange ) { 3895 makeValid(); 3896 ngModelCtrl.$setViewValue( new Date(selected) ); 3897 updateTemplate( keyboardChange ); 3898 } 3899 3900 function makeValid() { 3901 ngModelCtrl.$setValidity('time', true); 3902 $scope.invalidHours = false;
3903 $scope.invalidMinutes = false; 3904 } 3905 3906 function updateTemplate( keyboardChange ) { 3907 var hours = selected.getHours(), minutes = selected.getMinutes(); 3908 3909 if ( $scope.showMeridian ) { 3910 hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system 3911 } 3912 3913 $scope.hours = keyboardChange === 'h' ? hours : pad(hours); 3914 if (keyboardChange !== 'm') { 3915 $scope.minutes = pad(minutes); 3916 } 3917 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; 3918 } 3919 3920 function addMinutes( minutes ) { 3921 var dt = new Date( selected.getTime() + minutes * 60000 ); 3922 selected.setHours( dt.getHours(), dt.getMinutes() ); 3923 refresh(); 3924 } 3925 3926 $scope.incrementHours = function() { 3927 addMinutes( hourStep * 60 ); 3928 }; 3929 $scope.decrementHours = function() { 3930 addMinutes( - hourStep * 60 ); 3931 }; 3932 $scope.incrementMinutes = function() { 3933 addMinutes( minuteStep ); 3934 }; 3935 $scope.decrementMinutes = function() { 3936 addMinutes( - minuteStep ); 3937 }; 3938 $scope.toggleMeridian = function() { 3939 addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) ); 3940 }; 3941}]) 3942 3943.directive('timepicker', function () { 3944 return { 3945 restrict: 'EA', 3946 require: ['timepicker', '?^ngModel'], 3947 controller:'TimepickerController', 3948 replace: true, 3949 scope: {}, 3950 templateUrl: 'template/timepicker/timepicker.html', 3951 link: function(scope, element, attrs, ctrls) { 3952 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 3953 3954 if ( ngModelCtrl ) { 3955 timepickerCtrl.init( ngModelCtrl, element.find('input') ); 3956 } 3957 } 3958 }; 3959}); 3960 3961angular.module('ui.bootstrap.transition', []) 3962 3963.value('$transitionSuppressDeprecated', false) 3964/** 3965 * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete. 3966 * @param {DOMElement} element The DOMElement that will be animated. 3967 * @param {string|object|function} trigger The thing that will cause the transition to start: 3968 * - As a string, it represents the css class to be added to the element. 3969 * - As an object, it represents a hash of style attributes to be applied to the element. 3970 * - As a function, it represents a function to be called that will cause the transition to occur. 3971 * @return {Promise} A promise that is resolved when the transition finishes. 3972 */ 3973.factory('$transition', [ 3974 '$q', '$timeout', '$rootScope', '$log', '$transitionSuppressDeprecated', 3975function($q , $timeout , $rootScope , $log , $transitionSuppressDeprecated) { 3976 3977 if (!$transitionSuppressDeprecated) { 3978 $log.warn('$transition is now deprecated. Use $animate from ngAnimate instead.'); 3979 } 3980 3981 var $transition = function(element, trigger, options) { 3982 options = options || {}; 3983 var deferred = $q.defer(); 3984 var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName']; 3985 3986 var transitionEndHandler = function(event) { 3987 $rootScope.$apply(function() { 3988 element.unbind(endEventName, transitionEndHandler); 3989 deferred.resolve(element); 3990 }); 3991 }; 3992 3993 if (endEventName) { 3994 element.bind(endEventName, transitionEndHandler); 3995 } 3996 3997 // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur 3998 $timeout(function() { 3999 if ( angular.isString(trigger) ) { 4000 element.addClass(trigger); 4001 } else if ( angular.isFunction(trigger) ) { 4002 trigger(element); 4003 } else if ( angular.isObject(trigger) ) { 4004 element.css(trigger); 4005 } 4006 //If browser does not support transitions, instantly resolve 4007 if ( !endEventName ) { 4008 deferred.resolve(element); 4009 } 4010 }); 4011 4012 // Add our custom cancel function to the promise that is returned 4013 // We can call this if we are about to run a new transition, which we know will prevent this transition from ending, 4014 // i.e. it will therefore never raise a transitionEnd event for that transition 4015 deferred.promise.cancel = function() { 4016 if ( endEventName ) { 4017 element.unbind(endEventName, transitionEndHandler); 4018 } 4019 deferred.reject('Transition cancelled'); 4020 }; 4021 4022 return deferred.promise; 4023 }; 4024 4025 // Work out the name of the transitionEnd event 4026 var transElement = document.createElement('trans'); 4027 var transitionEndEventNames = { 4028 'WebkitTransition': 'webkitTransitionEnd', 4029 'MozTransition': 'transitionend', 4030 'OTransition': 'oTransitionEnd', 4031 'transition': 'transitionend' 4032 }; 4033 var animationEndEventNames = { 4034 'WebkitTransition': 'webkitAnimationEnd', 4035 'MozTransition': 'animationend', 4036 'OTransition': 'oAnimationEnd', 4037 'transition': 'animationend' 4038 }; 4039 function findEndEventName(endEventNames) { 4040 for (var name in endEventNames){ 4041 if (transElement.style[name] !== undefined) { 4042 return endEventNames[name]; 4043 } 4044 } 4045 } 4046 $transition.transitionEndEventName = findEndEventName(transitionEndEventNames); 4047 $transition.animationEndEventName = findEndEventName(animationEndEventNames); 4048 return $transition; 4049}]); 4050 4051angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml']) 4052 4053/**
4054 * A helper service that can parse typeahead's syntax (string provided by users) 4055 * Extracted to a separate service for ease of unit testing 4056 */ 4057 .factory('typeaheadParser', ['$parse', function ($parse) { 4058 4059 // 00000111000000000000022200000000000000003333333333333330000000000044000 4060 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; 4061 4062 return { 4063 parse:function (input) { 4064 4065 var match = input.match(TYPEAHEAD_REGEXP); 4066 if (!match) { 4067 throw new Error( 4068 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + 4069 ' but got "' + input + '".'); 4070 } 4071 4072 return { 4073 itemName:match[3], 4074 source:$parse(match[4]), 4075 viewMapper:$parse(match[2] || match[1]), 4076 modelMapper:$parse(match[1]) 4077 }; 4078 } 4079 }; 4080}]) 4081 4082 .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', 4083 function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) { 4084 4085 var HOT_KEYS = [9, 13, 27, 38, 40]; 4086 4087 return { 4088 require:'ngModel', 4089 link:function (originalScope, element, attrs, modelCtrl) { 4090 4091 //SUPPORTED ATTRIBUTES (OPTIONS) 4092 4093 //minimal no of characters that needs to be entered before typeahead kicks-in 4094 var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1; 4095 4096 //minimal wait time after last character typed before typeahead kicks-in 4097 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; 4098 4099 //should it restrict model values to the ones selected from the popup only? 4100 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; 4101 4102 //binding to a variable that indicates if matches are being retrieved asynchronously 4103 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; 4104 4105 //a callback executed when a match is selected 4106 var onSelectCallback = $parse(attrs.typeaheadOnSelect); 4107 4108 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; 4109 4110 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; 4111 4112 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; 4113 4114 //INTERNAL VARIABLES 4115 4116 //model setter executed upon match selection 4117 var $setModelValue = $parse(attrs.ngModel).assign; 4118 4119 //expressions used by typeahead 4120 var parserResult = typeaheadParser.parse(attrs.typeahead); 4121 4122 var hasFocus; 4123 4124 //create a child scope for the typeahead directive so we are not polluting original scope 4125 //with typeahead-specific data (matches, query etc.) 4126 var scope = originalScope.$new(); 4127 originalScope.$on('$destroy', function(){ 4128 scope.$destroy(); 4129 }); 4130 4131 // WAI-ARIA 4132 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); 4133 element.attr({ 4134 'aria-autocomplete': 'list', 4135 'aria-expanded': false, 4136 'aria-owns': popupId 4137 }); 4138 4139 //pop-up element used to display matches 4140 var popUpEl = angular.element('<div typeahead-popup></div>'); 4141 popUpEl.attr({ 4142 id: popupId, 4143 matches: 'matches', 4144 active: 'activeIdx', 4145 select: 'select(activeIdx)', 4146 query: 'query', 4147 position: 'position' 4148 }); 4149 //custom item template 4150 if (angular.isDefined(attrs.typeaheadTemplateUrl)) { 4151 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); 4152 } 4153 4154 var resetMatches = function() { 4155 scope.matches = []; 4156 scope.activeIdx = -1; 4157 element.attr('aria-expanded', false); 4158 }; 4159 4160 var getMatchId = function(index) { 4161 return popupId + '-option-' + index; 4162 }; 4163 4164 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. 4165 // This attribute is added or removed automatically when the `activeIdx` changes. 4166 scope.$watch('activeIdx', function(index) { 4167 if (index < 0) { 4168 element.removeAttr('aria-activedescendant'); 4169 } else { 4170 element.attr('aria-activedescendant', getMatchId(index)); 4171 } 4172 }); 4173 4174 var getMatchesAsync = function(inputValue) { 4175 4176 var locals = {$viewValue: inputValue}; 4177 isLoadingSetter(originalScope, true); 4178 $q.when(parserResult.source(originalScope, locals)).then(function(matches) { 4179 4180 //it might happen that several async queries were in progress if a user were typing fast 4181 //but we are interested only in responses that correspond to the current view value 4182 var onCurrentRequest = (inputValue === modelCtrl.$viewValue); 4183 if (onCurrentRequest && hasFocus) { 4184 if (matches && matches.length > 0) { 4185
4186 scope.activeIdx = focusFirst ? 0 : -1; 4187 scope.matches.length = 0; 4188 4189 //transform labels 4190 for(var i=0; i<matches.length; i++) { 4191 locals[parserResult.itemName] = matches[i]; 4192 scope.matches.push({ 4193 id: getMatchId(i), 4194 label: parserResult.viewMapper(scope, locals), 4195 model: matches[i] 4196 }); 4197 } 4198 4199 scope.query = inputValue; 4200 //position pop-up with matches - we need to re-calculate its position each time we are opening a window 4201 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page 4202 //due to other elements being rendered 4203 scope.position = appendToBody ? $position.offset(element) : $position.position(element); 4204 scope.position.top = scope.position.top + element.prop('offsetHeight'); 4205 4206 element.attr('aria-expanded', true); 4207 } else { 4208 resetMatches(); 4209 } 4210 } 4211 if (onCurrentRequest) { 4212 isLoadingSetter(originalScope, false); 4213 } 4214 }, function(){ 4215 resetMatches(); 4216 isLoadingSetter(originalScope, false); 4217 }); 4218 }; 4219 4220 resetMatches(); 4221 4222 //we need to propagate user's query so we can higlight matches 4223 scope.query = undefined; 4224 4225 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later 4226 var timeoutPromise; 4227 4228 var scheduleSearchWithTimeout = function(inputValue) { 4229 timeoutPromise = $timeout(function () { 4230 getMatchesAsync(inputValue); 4231 }, waitTime); 4232 }; 4233 4234 var cancelPreviousTimeout = function() { 4235 if (timeoutPromise) { 4236 $timeout.cancel(timeoutPromise); 4237 } 4238 }; 4239 4240 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM 4241 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue 4242 modelCtrl.$parsers.unshift(function (inputValue) { 4243 4244 hasFocus = true; 4245 4246 if (inputValue && inputValue.length >= minSearch) { 4247 if (waitTime > 0) { 4248 cancelPreviousTimeout(); 4249 scheduleSearchWithTimeout(inputValue); 4250 } else { 4251 getMatchesAsync(inputValue); 4252 } 4253 } else { 4254 isLoadingSetter(originalScope, false); 4255 cancelPreviousTimeout(); 4256 resetMatches(); 4257 } 4258 4259 if (isEditable) { 4260 return inputValue; 4261 } else { 4262 if (!inputValue) { 4263 // Reset in case user had typed something previously. 4264 modelCtrl.$setValidity('editable', true); 4265 return inputValue; 4266 } else { 4267 modelCtrl.$setValidity('editable', false); 4268 return undefined; 4269 } 4270 } 4271 }); 4272 4273 modelCtrl.$formatters.push(function (modelValue) { 4274 4275 var candidateViewValue, emptyViewValue; 4276 var locals = {}; 4277 4278 // The validity may be set to false via $parsers (see above) if 4279 // the model is restricted to selected values. If the model 4280 // is set manually it is considered to be valid. 4281 if (!isEditable) { 4282 modelCtrl.$setValidity('editable', true); 4283 } 4284 4285 if (inputFormatter) { 4286 4287 locals.$model = modelValue; 4288 return inputFormatter(originalScope, locals); 4289 4290 } else { 4291 4292 //it might happen that we don't have enough info to properly render input value 4293 //we need to check for this situation and simply return model value if we can't apply custom formatting 4294 locals[parserResult.itemName] = modelValue; 4295 candidateViewValue = parserResult.viewMapper(originalScope, locals); 4296 locals[parserResult.itemName] = undefined; 4297 emptyViewValue = parserResult.viewMapper(originalScope, locals); 4298 4299 return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue; 4300 } 4301 }); 4302 4303 scope.select = function (activeIdx) { 4304 //called from within the $digest() cycle 4305 var locals = {}; 4306 var model, item; 4307 4308 locals[parserResult.itemName] = item = scope.matches[activeIdx].model; 4309 model = parserResult.modelMapper(originalScope, locals); 4310 $setModelValue(originalScope, model); 4311 modelCtrl.$setValidity('editable', true); 4312 modelCtrl.$setValidity('parse', true); 4313 4314 onSelectCallback(originalScope, { 4315 $item: item, 4316 $model: model, 4317 $label: parserResult.viewMapper(originalScope, locals) 4318 }); 4319 4320 resetMatches(); 4321 4322 //return focus to the input element if a match was selected via a mouse click event 4323 // use timeout to avoid $rootScope:inprog error 4324 $timeout(function() { element[0].focus(); }, 0, false); 4325 }; 4326 4327 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) 4328 element.bind('keydown', function (evt) { 4329 4330 //typeahead is open and an "interesting" key was pressed 4331 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { 4332 return; 4333 } 4334 4335 // if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything 4336 if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) { 4337 return; 4338 } 4339 4340 evt.preventDefault(); 4341 4342 if (evt.which === 40) { 4343 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; 4344 scope.$digest(); 4345 4346 } else if (evt.which === 38) { 4347 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; 4348 scope.$digest(); 4349 4350 } else if (evt.which === 13 || evt.which === 9) { 4351 scope.$apply(function () { 4352 scope.select(scope.activeIdx); 4353 }); 4354 4355 } else if (evt.which === 27) { 4356 evt.stopPropagation(); 4357 4358 resetMatches(); 4359 scope.$digest(); 4360 } 4361 }); 4362 4363 element.bind('blur', function (evt) { 4364 hasFocus = false;
4365 }); 4366 4367 // Keep reference to click handler to unbind it. 4368 var dismissClickHandler = function (evt) { 4369 if (element[0] !== evt.target) { 4370 resetMatches(); 4371 scope.$digest(); 4372 } 4373 }; 4374 4375 $document.bind('click', dismissClickHandler); 4376 4377 originalScope.$on('$destroy', function(){ 4378 $document.unbind('click', dismissClickHandler); 4379 if (appendToBody) { 4380 $popup.remove(); 4381 } 4382 // Prevent jQuery cache memory leak 4383 popUpEl.remove(); 4384 }); 4385 4386 var $popup = $compile(popUpEl)(scope); 4387 4388 if (appendToBody) { 4389 $document.find('body').append($popup); 4390 } else { 4391 element.after($popup); 4392 } 4393 } 4394 }; 4395 4396}]) 4397 4398 .directive('typeaheadPopup', function () { 4399 return { 4400 restrict:'EA', 4401 scope:{ 4402 matches:'=', 4403 query:'=', 4404 active:'=', 4405 position:'=', 4406 select:'&' 4407 }, 4408 replace:true, 4409 templateUrl:'template/typeahead/typeahead-popup.html', 4410 link:function (scope, element, attrs) { 4411 4412 scope.templateUrl = attrs.templateUrl; 4413 4414 scope.isOpen = function () { 4415 return scope.matches.length > 0; 4416 }; 4417 4418 scope.isActive = function (matchIdx) { 4419 return scope.active == matchIdx; 4420 }; 4421 4422 scope.selectActive = function (matchIdx) { 4423 scope.active = matchIdx; 4424 }; 4425 4426 scope.selectMatch = function (activeIdx) { 4427 scope.select({activeIdx:activeIdx}); 4428 }; 4429 } 4430 }; 4431 }) 4432 4433 .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) { 4434 return { 4435 restrict:'EA', 4436 scope:{ 4437 index:'=', 4438 match:'=', 4439 query:'=' 4440 }, 4441 link:function (scope, element, attrs) { 4442 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html'; 4443 $templateRequest(tplUrl).then(function(tplContent) { 4444 $compile(tplContent.trim())(scope, function(clonedElement){ 4445 element.replaceWith(clonedElement); 4446 }); 4447 }); 4448 } 4449 }; 4450 }]) 4451 4452 .filter('typeaheadHighlight', function() { 4453 4454 function escapeRegexp(queryToEscape) { 4455 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); 4456 } 4457 4458 return function(matchItem, query) { 4459 return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; 4460 }; 4461 }); 4462 4463angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { 4464 $templateCache.put("template/accordion/accordion-group.html", 4465 "<div class=\"panel panel-default\">\n" + 4466 " <div class=\"panel-heading\">\n" + 4467 " <h4 class=\"panel-title\">\n" + 4468 " <a href=\"javascript:void(0)\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" + 4469 " </h4>\n" + 4470 " </div>\n" + 4471 " <div class=\"panel-collapse collapse\" collapse=\"!isOpen\">\n" + 4472 " <div class=\"panel-body\" ng-transclude></div>\n" + 4473 " </div>\n" + 4474 "</div>\n" + 4475 ""); 4476}]); 4477 4478angular.module("template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { 4479 $templateCache.put("template/accordion/accordion.html", 4480 "<div class=\"panel-group\" ng-transclude></div>"); 4481}]); 4482 4483angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) { 4484 $templateCache.put("template/alert/alert.html", 4485 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissable' : null]\" role=\"alert\">\n" + 4486 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close()\">\n" + 4487 " <span aria-hidden=\"true\">×</span>\n" + 4488 " <span class=\"sr-only\">Close</span>\n" + 4489 " </button>\n" + 4490 " <div ng-transclude></div>\n" + 4491 "</div>\n" + 4492 ""); 4493}]); 4494 4495angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { 4496 $templateCache.put("template/carousel/carousel.html", 4497 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" + 4498 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" + 4499 " <li ng-repeat=\"slide in slides | orderBy:'index' track by $index\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" + 4500 " </ol>\n" + 4501 " <div class=\"carousel-inner\" ng-transclude></div>\n" + 4502 " <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-left\"></span></a>\n" + 4503 " <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-right\"></span></a>\n" + 4504 "</div>\n" + 4505 ""); 4506}]); 4507 4508angular.module("template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { 4509 $templateCache.put("template/carousel/slide.html", 4510 "<div ng-class=\"{\n" + 4511 " 'active': active\n" + 4512 " }\" class=\"item text-center\" ng-transclude></div>\n" + 4513 ""); 4514}]); 4515 4516angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { 4517 $templateCache.put("template/datepicker/datepicker.html", 4518 "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" + 4519 " <daypicker ng-switch-when=\"day\" tabindex=\"0\"></daypicker>\n" + 4520 " <monthpicker ng-switch-when=\"month\" tabindex=\"0\"></monthpicker>\n" + 4521 " <yearpicker ng-switch-when=\"year\" tabindex=\"0\"></yearpicker>\n" + 4522 "</div>"); 4523}]); 4524 4525angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { 4526 $templateCache.put("template/datepicker/day.html", 4527 "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 4528 " <thead>\n" + 4529 " <tr>\n" + 4530 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 4531 " <th colspan=\"{{5 + showWeeks}}\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === max
4531Mode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + 4532 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 4533 " </tr>\n" + 4534 " <tr>\n" + 4535 " <th ng-show=\"showWeeks\" class=\"text-center\"></th>\n" + 4536 " <th ng-repeat=\"label in labels track by $index\" class=\"text-center\"><small aria-label=\"{{label.full}}\">{{label.abbr}}</small></th>\n" + 4537 " </tr>\n" + 4538 " </thead>\n" + 4539 " <tbody>\n" + 4540 " <tr ng-repeat=\"row in rows track by $index\">\n" + 4541 " <td ng-show=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" + 4542 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\" ng-class=\"dt.customClass\">\n" + 4543 " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-muted': dt.secondary, 'text-info': dt.current}\">{{dt.label}}</span></button>\n" + 4544 " </td>\n" + 4545 " </tr>\n" + 4546 " </tbody>\n" + 4547 "</table>\n" + 4548 ""); 4549}]); 4550 4551angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { 4552 $templateCache.put("template/datepicker/month.html", 4553 "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 4554 " <thead>\n" + 4555 " <tr>\n" + 4556 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 4557 " <th><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + 4558 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 4559 " </tr>\n" + 4560 " </thead>\n" + 4561 " <tbody>\n" + 4562 " <tr ng-repeat=\"row in rows track by $index\">\n" + 4563 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" + 4564 " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" + 4565 " </td>\n" + 4566 " </tr>\n" + 4567 " </tbody>\n" + 4568 "</table>\n" + 4569 ""); 4570}]); 4571 4572angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) { 4573 $templateCache.put("template/datepicker/popup.html", 4574 "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\">\n" + 4575 " <li ng-transclude></li>\n" + 4576 " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" + 4577 " <span class=\"btn-group pull-left\">\n" + 4578 " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\">{{ getText('current') }}</button>\n" + 4579 " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" + 4580 " </span>\n" + 4581 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" + 4582 " </li>\n" + 4583 "</ul>\n" + 4584 ""); 4585}]); 4586 4587angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { 4588 $templateCache.put("template/datepicker/year.html", 4589 "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 4590 " <thead>\n" + 4591 " <tr>\n" + 4592 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 4593 " <th colspan=\"3\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === max
4593Mode\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" + 4594 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 4595 " </tr>\n" + 4596 " </thead>\n" + 4597 " <tbody>\n" + 4598 " <tr ng-repeat=\"row in rows track by $index\">\n" + 4599 " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" + 4600 " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" + 4601 " </td>\n" + 4602 " </tr>\n" + 4603 " </tbody>\n" + 4604 "</table>\n" + 4605 ""); 4606}]); 4607 4608angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { 4609 $templateCache.put("template/modal/backdrop.html", 4610 "<div class=\"modal-backdrop\"\n" + 4611 " modal-animation-class=\"fade\"\n" + 4612 " ng-class=\"{in: animate}\"\n" + 4613 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" + 4614 "></div>\n" + 4615 ""); 4616}]); 4617 4618angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) { 4619 $templateCache.put("template/modal/window.html", 4620 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" + 4621 " modal-animation-class=\"fade\"\n" + 4622 " ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" + 4623 " <div class=\"modal-dialog\" ng-class=\"size ? 'modal-' + size : ''\"><div class=\"modal-content\" modal-transclude></div></div>\n" + 4624 "</div>\n" + 4625 ""); 4626}]); 4627 4628angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) { 4629 $templateCache.put("template/pagination/pager.html", 4630 "<ul class=\"pager\">\n" + 4631 " <li ng-class=\"{disabled: noPrevious(), previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{getText('previous')}}</a></li>\n" + 4632 " <li ng-class=\"{disabled: noNext(), next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{getText('next')}}</a></li>\n" + 4633 "</ul>"); 4634}]); 4635 4636angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { 4637 $templateCache.put("template/pagination/pagination.html", 4638 "<ul class=\"pagination\">\n" + 4639 " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(1, $event)\">{{getText('first')}}</a></li>\n" + 4640 " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{getText('previous')}}</a></li>\n" + 4641 " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\"><a href ng-click=\"selectPage(page.number, $event)\">{{page.text}}</a></li>\n" + 4642 " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{getText('next')}}</a></li>\n" + 4643 " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(totalPages, $event)\">{{getText('last')}}</a></li>\n" + 4644 "</ul>"); 4645}]); 4646 4647angular.module("template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) { 4648 $templateCache.put("template/tooltip/tooltip-html-popup.html", 4649 "<div class=\"tooltip\"\n" + 4650 " tooltip-animation-class=\"fade\"\n" + 4651 " tooltip-classes\n" + 4652 " ng-class=\"{ in: isOpen() }\">\n" + 4653 " <div class=\"tooltip-arrow\"></div>\n" + 4654 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" + 4655 "</div>\n" + 4656 ""); 4657}]); 4658 4659angular.module("template/tooltip/tooltip-html-unsafe-popup.html", []).run(["$templateCache", function($templateCache) { 4660 $templateCache.put("template/tooltip/tooltip-html-unsafe-popup.html", 4661 "<div class=\"tooltip\"\n" + 4662 " tooltip-animation-class=\"fade\"\n" +
4663 " tooltip-classes\n" + 4664 " ng-class=\"{ in: isOpen() }\">\n" + 4665 " <div class=\"tooltip-arrow\"></div>\n" + 4666 " <div class=\"tooltip-inner\" bind-html-unsafe=\"content\"></div>\n" + 4667 "</div>\n" + 4668 ""); 4669}]); 4670 4671angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { 4672 $templateCache.put("template/tooltip/tooltip-popup.html", 4673 "<div class=\"tooltip\"\n" + 4674 " tooltip-animation-class=\"fade\"\n" + 4675 " tooltip-classes\n" + 4676 " ng-class=\"{ in: isOpen() }\">\n" + 4677 " <div class=\"tooltip-arrow\"></div>\n" + 4678 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" + 4679 "</div>\n" + 4680 ""); 4681}]); 4682 4683angular.module("template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) { 4684 $templateCache.put("template/tooltip/tooltip-template-popup.html", 4685 "<div class=\"tooltip\"\n" + 4686 " tooltip-animation-class=\"fade\"\n" + 4687 " tooltip-classes\n" + 4688 " ng-class=\"{ in: isOpen() }\">\n" + 4689 " <div class=\"tooltip-arrow\"></div>\n" + 4690 " <div class=\"tooltip-inner\"\n" + 4691 " tooltip-template-transclude=\"contentExp()\"\n" + 4692 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" + 4693 "</div>\n" + 4694 ""); 4695}]); 4696 4697angular.module("template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) { 4698 $templateCache.put("template/popover/popover-template.html", 4699 "<div class=\"popover\"\n" + 4700 " tooltip-animation-class=\"fade\"\n" + 4701 " tooltip-classes\n" + 4702 " ng-class=\"{ in: isOpen() }\">\n" + 4703 " <div class=\"arrow\"></div>\n" + 4704 "\n" + 4705 " <div class=\"popover-inner\">\n" + 4706 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" + 4707 " <div class=\"popover-content\"\n" + 4708 " tooltip-template-transclude=\"contentExp()\"\n" + 4709 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" + 4710 " </div>\n" + 4711 "</div>\n" + 4712 ""); 4713}]); 4714 4715angular.module("template/popover/popover-window.html", []).run(["$templateCache", function($templateCache) { 4716 $templateCache.put("template/popover/popover-window.html", 4717 "<div class=\"popover {{placement}}\" ng-class=\"{ in: isOpen, fade: animation }\">\n" + 4718 " <div class=\"arrow\"></div>\n" + 4719 "\n" + 4720 " <div class=\"popover-inner\">\n" + 4721 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-show=\"title\"></h3>\n" + 4722 " <div class=\"popover-content\" tooltip-template-transclude></div>\n" + 4723 " </div>\n" + 4724 "</div>\n" + 4725 ""); 4726}]); 4727 4728angular.module("template/popover/popover.html", []).run(["$templateCache", function($templateCache) { 4729 $templateCache.put("template/popover/popover.html", 4730 "<div class=\"popover\"\n" + 4731 " tooltip-animation-class=\"fade\"\n" + 4732 " tooltip-classes\n" + 4733 " ng-class=\"{ in: isOpen() }\">\n" + 4734 " <div class=\"arrow\"></div>\n" + 4735 "\n" + 4736 " <div class=\"popover-inner\">\n" + 4737 " <h3 class=\"popover-title\" ng-bind=\"title\" ng-if=\"title\"></h3>\n" + 4738 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" + 4739 " </div>\n" + 4740 "</div>\n" + 4741 ""); 4742}]); 4743 4744angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { 4745 $templateCache.put("template/progressbar/bar.html", 4746 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>\n" + 4747 ""); 4748}]); 4749 4750angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { 4751 $templateCache.put("template/progressbar/progress.html", 4752 "<div class=\"progress\" ng-transclude></div>"); 4753}]); 4754 4755angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { 4756 $templateCache.put("template/progressbar/progressbar.html", 4757 "<div class=\"progress\">\n" + 4758 " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>\n" + 4759 "</div>\n" + 4760 ""); 4761}]); 4762 4763angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) { 4764 $templateCache.put("template/rating/rating.html", 4765 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" + 4766 " <i ng-repeat=\"r in range track by $index\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\">\n" +
4767 " <span class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" + 4768 " </i>\n" + 4769 "</span>"); 4770}]); 4771 4772angular.module("template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { 4773 $templateCache.put("template/tabs/tab.html", 4774 "<li ng-class=\"{active: active, disabled: disabled}\">\n" + 4775 " <a href ng-click=\"select()\" tab-heading-transclude>{{heading}}</a>\n" + 4776 "</li>\n" + 4777 ""); 4778}]); 4779 4780angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { 4781 $templateCache.put("template/tabs/tabset.html", 4782 "<div>\n" + 4783 " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + 4784 " <div class=\"tab-content\">\n" + 4785 " <div class=\"tab-pane\" \n" + 4786 " ng-repeat=\"tab in tabs\" \n" + 4787 " ng-class=\"{active: tab.active}\"\n" + 4788 " tab-content-transclude=\"tab\">\n" + 4789 " </div>\n" + 4790 " </div>\n" + 4791 "</div>\n" + 4792 ""); 4793}]); 4794 4795angular.module("template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { 4796 $templateCache.put("template/timepicker/timepicker.html", 4797 "<table>\n" + 4798 " <tbody>\n" + 4799 " <tr class=\"text-center\">\n" + 4800 " <td><a ng-click=\"incrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 4801 " <td> </td>\n" + 4802 " <td><a ng-click=\"incrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 4803 " <td ng-show=\"showMeridian\"></td>\n" + 4804 " </tr>\n" + 4805 " <tr>\n" + 4806 " <td class=\"form-group\" ng-class=\"{'has-error': invalidHours}\">\n" + 4807 " <input style=\"width:50px;\" type=\"text\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" + 4808 " </td>\n" + 4809 " <td>:</td>\n" + 4810 " <td class=\"form-group\" ng-class=\"{'has-error': invalidMinutes}\">\n" + 4811 " <input style=\"width:50px;\" type=\"text\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\">\n" + 4812 " </td>\n" + 4813 " <td ng-show=\"showMeridian\"><button type=\"button\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\">{{meridian}}</button></td>\n" + 4814 " </tr>\n" + 4815 " <tr class=\"text-center\">\n" + 4816 " <td><a ng-click=\"decrementHours()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 4817 " <td> </td>\n" + 4818 " <td><a ng-click=\"decrementMinutes()\" class=\"btn btn-link\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 4819 " <td ng-show=\"showMeridian\"></td>\n" + 4820 " </tr>\n" + 4821 " </tbody>\n" + 4822 "</table>\n" + 4823 ""); 4824}]); 4825 4826angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { 4827 $templateCache.put("template/typeahead/typeahead-match.html", 4828 "<a tabindex=\"-1\" bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>"); 4829}]); 4830 4831angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { 4832 $templateCache.put("template/typeahead/typeahead-popup.html", 4833 "<ul class=\"dropdown-menu\" ng-show=\"isOpen()\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" + 4834 " <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{match.id}}\">\n" + 4835 " <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" + 4836 " </li>\n" + 4837 "</ul>\n" + 4838 ""); 4839}]); 4840!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>');
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.