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