1/* 2 * angular-ui-bootstrap 3 * http://angular-ui.github.io/bootstrap/ 4 5 * Version: 1.3.3 - 2016-05-22 6 * License: MIT 7 */angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]); 8angular.module("ui.bootstrap.tpls", ["uib/template/accordion/accordion-group.html","uib/template/accordion/accordion.html","uib/template/alert/alert.html","uib/template/carousel/carousel.html","uib/template/carousel/slide.html","uib/template/datepicker/datepicker.html","uib/template/datepicker/day.html","uib/template/datepicker/month.html","uib/template/datepicker/year.html","uib/template/datepickerPopup/popup.html","uib/template/modal/backdrop.html","uib/template/modal/window.html","uib/template/pager/pager.html","uib/template/pagination/pagination.html","uib/template/tooltip/tooltip-html-popup.html","uib/template/tooltip/tooltip-popup.html","uib/template/tooltip/tooltip-template-popup.html","uib/template/popover/popover-html.html","uib/template/popover/popover-template.html","uib/template/popover/popover.html","uib/template/progressbar/bar.html","uib/template/progressbar/progress.html","uib/template/progressbar/progressbar.html","uib/template/rating/rating.html","uib/template/tabs/tab.html","uib/template/tabs/tabset.html","uib/template/timepicker/timepicker.html","uib/template/typeahead/typeahead-match.html","uib/template/typeahead/typeahead-popup.html"]); 9angular.module('ui.bootstrap.collapse', []) 10 11 .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) { 12 var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null; 13 return { 14 link: function(scope, element, attrs) { 15 var expandingExpr = $parse(attrs.expanding), 16 expandedExpr = $parse(attrs.expanded), 17 collapsingExpr = $parse(attrs.collapsing), 18 collapsedExpr = $parse(attrs.collapsed); 19 20 if (!scope.$eval(attrs.uibCollapse)) { 21 element.addClass('in') 22 .addClass('collapse') 23 .attr('aria-expanded', true) 24 .attr('aria-hidden', false) 25 .css({height: 'auto'}); 26 } 27 28 function expand() { 29 if (element.hasClass('collapse') && element.hasClass('in')) { 30 return; 31 } 32 33 $q.resolve(expandingExpr(scope)) 34 .then(function() { 35 element.removeClass('collapse') 36 .addClass('collapsing') 37 .attr('aria-expanded', true) 38 .attr('aria-hidden', false); 39 40 if ($animateCss) { 41 $animateCss(element, { 42 addClass: 'in', 43 easing: 'ease', 44 to: { height: element[0].scrollHeight + 'px' } 45 }).start()['finally'](expandDone); 46 } else { 47 $animate.addClass(element, 'in', { 48 to: { height: element[0].scrollHeight + 'px' } 49 }).then(expandDone); 50 } 51 }); 52 } 53 54 function expandDone() { 55 element.removeClass('collapsing') 56 .addClass('collapse') 57 .css({height: 'auto'}); 58 expandedExpr(scope); 59 } 60 61 function collapse() { 62 if (!element.hasClass('collapse') && !element.hasClass('in')) { 63 return collapseDone(); 64 } 65 66 $q.resolve(collapsingExpr(scope)) 67 .then(function() { 68 element 69 // IMPORTANT: The height must be set before adding "collapsing" class. 70 // Otherwise, the browser attempts to animate from height 0 (in
vendor: 62,654 bytes, lines 71-2089
71 // collapsing class) to the given height here. 72 .css({height: element[0].scrollHeight + 'px'}) 73 // initially all panel collapse have the collapse class, this removal 74 // prevents the animation from jumping to collapsed state 75 .removeClass('collapse') 76 .addClass('collapsing') 77 .attr('aria-expanded', false) 78 .attr('aria-hidden', true); 79 80 if ($animateCss) { 81 $animateCss(element, { 82 removeClass: 'in', 83 to: {height: '0'} 84 }).start()['finally'](collapseDone); 85 } else { 86 $animate.removeClass(element, 'in', { 87 to: {height: '0'} 88 }).then(collapseDone); 89 } 90 }); 91 } 92 93 function collapseDone() { 94 element.css({height: '0'}); // Required so that collapse works when animation is disabled 95 element.removeClass('collapsing') 96 .addClass('collapse'); 97 collapsedExpr(scope); 98 } 99 100 scope.$watch(attrs.uibCollapse, function(shouldCollapse) { 101 if (shouldCollapse) { 102 collapse(); 103 } else { 104 expand(); 105 } 106 }); 107 } 108 }; 109 }]); 110 111angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse']) 112 113.constant('uibAccordionConfig', { 114 closeOthers: true 115}) 116 117.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) { 118 // This array keeps track of the accordion groups 119 this.groups = []; 120 121 // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to 122 this.closeOthers = function(openGroup) { 123 var closeOthers = angular.isDefined($attrs.closeOthers) ? 124 $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers; 125 if (closeOthers) { 126 angular.forEach(this.groups, function(group) { 127 if (group !== openGroup) { 128 group.isOpen = false; 129 } 130 }); 131 } 132 }; 133 134 // This is called from the accordion-group directive to add itself to the accordion 135 this.addGroup = function(groupScope) { 136 var that = this; 137 this.groups.push(groupScope); 138 139 groupScope.$on('$destroy', function(event) { 140 that.removeGroup(groupScope); 141 }); 142 }; 143 144 // This is called from the accordion-group directive when to remove itself 145 this.removeGroup = function(group) { 146 var index = this.groups.indexOf(group); 147 if (index !== -1) { 148 this.groups.splice(index, 1); 149 } 150 }; 151}]) 152 153// The accordion directive simply sets up the directive controller 154// and adds an accordion CSS class to itself element. 155.directive('uibAccordion', function() { 156 return { 157 controller: 'UibAccordionController', 158 controllerAs: 'accordion', 159 transclude: true, 160 templateUrl: function(element, attrs) { 161 return attrs.templateUrl || 'uib/template/accordion/accordion.html'; 162 } 163 }; 164}) 165 166// The accordion-group directive indicates a block of html that will expand and collapse in an accordion 167.directive('uibAccordionGroup', function() { 168 return { 169 require: '^uibAccordion', // We need this directive to be inside an accordion 170 transclude: true, // It transcludes the contents of the directive into the template 171 replace: true, // The element containing the directive will be replaced with the template 172 templateUrl: function(element, attrs) { 173 return attrs.templateUrl || 'uib/template/accordion/accordion-group.html'; 174 }, 175 scope: { 176 heading: '@', // Interpolate the heading attribute onto this scope 177 panelClass: '@?', // Ditto with panelClass 178 isOpen: '=?', 179 isDisabled: '=?' 180 }, 181 controller: function() { 182 this.setHeading = function(element) { 183 this.heading = element; 184 }; 185 }, 186 link: function(scope, element, attrs, accordionCtrl) { 187 accordionCtrl.addGroup(scope); 188 189 scope.openClass = attrs.openClass || 'panel-open'; 190 scope.panelClass = attrs.panelClass || 'panel-default'; 191 scope.$watch('isOpen', function(value) { 192 element.toggleClass(scope.openClass, !!value); 193 if (value) { 194 accordionCtrl.closeOthers(scope); 195 } 196 }); 197 198 scope.toggleOpen = function($event) { 199 if (!scope.isDisabled) { 200 if (!$event || $event.which === 32) { 201 scope.isOpen = !scope.isOpen; 202 } 203 } 204 }; 205 206 var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000); 207 scope.headingId = id + '-tab'; 208 scope.panelId = id + '-panel'; 209 } 210 }; 211}) 212 213// Use accordion-heading below an accordion-group to provide a heading containing HTML 214.directive('uibAccordionHeading', function() { 215 return { 216 transclude: true, // Grab the contents to be used as the heading 217 template: '', // In effect remove this element! 218 replace: true, 219 require: '^uibAccordionGroup', 220 link: function(scope, element, attrs, accordionGroupCtrl, transclude) { 221 // Pass the heading to the accordion-group controller 222 // so that it can be transcluded into the right place in the template 223 // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat] 224 accordionGroupCtrl.setHeading(transclude(scope, angular.noop)); 225 } 226 }; 227}) 228 229// Use in the accordion-group template to indicate where you want the heading to be transcluded 230// You must provide the property on the accordion-group controller that will hold the transcluded element 231.directive('uibAccordionTransclude', function() { 232 return { 233 require: '^uibAccordionGroup', 234 link: function(scope, element, attrs, controller) { 235 scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) { 236 if (heading) { 237 var elem = angular.element(element[0].querySelector(getHeaderSelectors())); 238 elem.html(''); 239 elem.append(heading); 240 } 241 }); 242 } 243 }; 244 245 function getHeaderSelectors() { 246 return 'uib-accordion-header,' + 247 'data-uib-accordion-header,' + 248 'x-uib-accordion-header,' + 249 'uib\\:accordion-header,' + 250 '[uib-accordion-header],' + 251 '[data-uib-accordion-header],' + 252 '[x-uib-accordion-header]'; 253 } 254}); 255 256angular.module('ui.bootstrap.alert', []) 257 258.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) { 259 $scope.closeable = !!$attrs.close; 260 261 var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ? 262 $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null; 263 264 if (dismissOnTimeout) { 265 $timeout(function() { 266 $scope.close(); 267 }, parseInt(dismissOnTimeout, 10)); 268 } 269}]) 270 271.directive('uibAlert', function() { 272 return { 273 controller: 'UibAlertController', 274 controllerAs: 'alert', 275 templateUrl: function(element, attrs) { 276 return attrs.templateUrl || 'uib/template/alert/alert.html'; 277 }, 278 transclude: true, 279 replace: true, 280 scope: { 281 type: '@', 282 close: '&' 283 } 284 }; 285}); 286 287angular.module('ui.bootstrap.buttons', []) 288 289.constant('uibButtonConfig', { 290 activeClass: 'active', 291 toggleEvent: 'click' 292}) 293 294.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) { 295 this.activeClass = buttonConfig.activeClass || 'active'; 296 this.toggleEvent = buttonConfig.toggleEvent || 'click'; 297}]) 298 299.directive('uibBtnRadio', ['$parse', function($parse) { 300 return { 301 require: ['uibBtnRadio', 'ngModel'], 302 controller: 'UibButtonsController', 303 controllerAs: 'buttons', 304 link: function(scope, element, attrs, ctrls) { 305 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 306 var uncheckableExpr = $parse(attrs.uibUncheckable); 307 308 element.find('input').css({display: 'none'}); 309 310 //model -> UI 311 ngModelCtrl.$render = function() { 312 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio))); 313 }; 314 315 //ui->model 316 element.on(buttonsCtrl.toggleEvent, function() { 317 if (attrs.disabled) { 318 return; 319 } 320 321 var isActive = element.hasClass(buttonsCtrl.activeClass); 322 323 if (!isActive || angular.isDefined(attrs.uncheckable)) { 324 scope.$apply(function() { 325 ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio)); 326 ngModelCtrl.$render(); 327 }); 328 } 329 }); 330 331 if (attrs.uibUncheckable) { 332 scope.$watch(uncheckableExpr, function(uncheckable) { 333 attrs.$set('uncheckable', uncheckable ? '' : undefined); 334 }); 335 } 336 } 337 }; 338}]) 339 340.directive('uibBtnCheckbox', function() { 341 return { 342 require: ['uibBtnCheckbox', 'ngModel'], 343 controller: 'UibButtonsController', 344 controllerAs: 'button', 345 link: function(scope, element, attrs, ctrls) { 346 var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 347 348 element.find('input').css({display: 'none'}); 349 350 function getTrueValue() { 351 return getCheckboxValue(attrs.btnCheckboxTrue, true); 352 } 353 354 function getFalseValue() { 355 return getCheckboxValue(attrs.btnCheckboxFalse, false); 356 } 357 358 function getCheckboxValue(attribute, defaultValue) { 359 return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue; 360 } 361 362 //model -> UI 363 ngModelCtrl.$render = function() { 364 element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue())); 365 }; 366 367 //ui->model 368 element.on(buttonsCtrl.toggleEvent, function() { 369 if (attrs.disabled) { 370 return; 371 } 372 373 scope.$apply(function() { 374 ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue()); 375 ngModelCtrl.$render(); 376 }); 377 }); 378 } 379 }; 380}); 381 382angular.module('ui.bootstrap.carousel', []) 383 384.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) { 385 var self = this, 386 slides = self.slides = $scope.slides = [], 387 SLIDE_DIRECTION = 'uib-slideDirection', 388 currentIndex = $scope.active, 389 currentInterval, isPlaying, bufferedTransitions = []; 390 391 var destroyed = false; 392 393 self.addSlide = function(slide, element) { 394 slides.push({ 395 slide: slide, 396 element: element 397 }); 398 slides.sort(function(a, b) { 399 return +a.slide.index - +b.slide.index; 400 }); 401 //if this is the first slide or the slide is set to active, select it 402 if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) { 403 if ($scope.$currentTransition) { 404 $scope.$currentTransition = null; 405 } 406 407 currentIndex = slide.index; 408 $scope.active = slide.index; 409 setActive(currentIndex); 410 self.select(slides[findSlideIndex(slide)]); 411 if (slides.length === 1) { 412 $scope.play(); 413 } 414 } 415 }; 416 417 self.getCurrentIndex = function() { 418 for (var i = 0; i < slides.length; i++) { 419 if (slides[i].slide.index === currentIndex) { 420 return i; 421 } 422 } 423 }; 424 425 self.next = $scope.next = function() { 426 var newIndex = (self.getCurrentIndex() + 1) % slides.length; 427 428 if (newIndex === 0 && $scope.noWrap()) { 429 $scope.pause(); 430 return; 431 } 432 433 return self.select(slides[newIndex], 'next'); 434 }; 435 436 self.prev = $scope.prev = function() { 437 var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1; 438 439 if ($scope.noWrap() && newIndex === slides.length - 1) { 440 $scope.pause(); 441 return; 442 } 443 444 return self.select(slides[newIndex], 'prev'); 445 }; 446 447 self.removeSlide = function(slide) { 448 var index = findSlideIndex(slide); 449 450 var bufferedIndex = bufferedTransitions.indexOf(slides[index]); 451 if (bufferedIndex !== -1) { 452 bufferedTransitions.splice(bufferedIndex, 1); 453 } 454 455 //get the index of the slide inside the carousel 456 slides.splice(index, 1); 457 if (slides.length > 0 && currentIndex === index) { 458 if (index >= slides.length) { 459 currentIndex = slides.length - 1; 460 $scope.active = currentIndex; 461 setActive(currentIndex); 462 self.select(slides[slides.length - 1]); 463 } else { 464 currentIndex = index; 465 $scope.active = currentIndex; 466 setActive(currentIndex); 467 self.select(slides[index]); 468 } 469 } else if (currentIndex > index) { 470 currentIndex--; 471 $scope.active = currentIndex; 472 } 473 474 //clean the active value when no more slide 475 if (slides.length === 0) { 476 currentIndex = null; 477 $scope.active = null; 478 clearBufferedTransitions(); 479 } 480 }; 481 482 /* direction: "prev" or "next" */ 483 self.select = $scope.select = function(nextSlide, direction) { 484 var nextIndex = findSlideIndex(nextSlide.slide); 485 //Decide direction if it's not given 486 if (direction === undefined) { 487 direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; 488 } 489 //Prevent this user-triggered transition from occurring if there is already one in progress 490 if (nextSlide.slide.index !== currentIndex && 491 !$scope.$currentTransition) { 492 goNext(nextSlide.slide, nextIndex, direction); 493 } else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) { 494 bufferedTransitions.push(slides[nextIndex]); 495 } 496 }; 497 498 /* Allow outside people to call indexOf on slides array */ 499 $scope.indexOfSlide = function(slide) { 500 return +slide.slide.index; 501 }; 502 503 $scope.isActive = function(slide) { 504 return $scope.active === slide.slide.index; 505 }; 506 507 $scope.isPrevDisabled = function() { 508 return $scope.active === 0 && $scope.noWrap(); 509 }; 510 511 $scope.isNextDisabled = function() { 512 return $scope.active === slides.length - 1 && $scope.noWrap(); 513 }; 514 515 $scope.pause = function() { 516 if (!$scope.noPause) { 517 isPlaying = false; 518 resetTimer(); 519 } 520 }; 521 522 $scope.play = function() { 523 if (!isPlaying) { 524 isPlaying = true; 525 restartTimer(); 526 } 527 }; 528 529 $scope.$on('$destroy', function() { 530 destroyed = true; 531 resetTimer(); 532 }); 533 534 $scope.$watch('noTransition', function(noTransition) { 535 $animate.enabled($element, !noTransition); 536 }); 537 538 $scope.$watch('interval', restartTimer); 539 540 $scope.$watchCollection('slides', resetTransition); 541 542 $scope.$watch('active', function(index) { 543 if (angular.isNumber(index) && currentIndex !== index) { 544 for (var i = 0; i < slides.length; i++) { 545 if (slides[i].slide.index === index) { 546 index = i; 547 break; 548 } 549 } 550 551 var slide = slides[index]; 552 if (slide) { 553 setActive(index); 554 self.select(slides[index]); 555 currentIndex = index; 556 } 557 } 558 }); 559 560 function clearBufferedTransitions() { 561 while (bufferedTransitions.length) { 562 bufferedTransitions.shift(); 563 } 564 } 565 566 function getSlideByIndex(index) { 567 for (var i = 0, l = slides.length; i < l; ++i) { 568 if (slides[i].index === index) { 569 return slides[i]; 570 } 571 } 572 } 573 574 function setActive(index) { 575 for (var i = 0; i < slides.length; i++) { 576 slides[i].slide.active = i === index; 577 } 578 } 579 580 function goNext(slide, index, direction) { 581 if (destroyed) { 582 return; 583 } 584 585 angular.extend(slide, {direction: direction}); 586 angular.extend(slides[currentIndex].slide || {}, {direction: direction}); 587 if ($animate.enabled($element) && !$scope.$currentTransition && 588 slides[index].element && self.slides.length > 1) { 589 slides[index].element.data(SLIDE_DIRECTION, slide.direction); 590 var currentIdx = self.getCurrentIndex(); 591 592 if (angular.isNumber(currentIdx) && slides[currentIdx].element) { 593 slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction); 594 } 595 596 $scope.$currentTransition = true; 597 $animate.on('addClass', slides[index].element, function(element, phase) { 598 if (phase === 'close') { 599 $scope.$currentTransition = null; 600 $animate.off('addClass', element); 601 if (bufferedTransitions.length) { 602 var nextSlide = bufferedTransitions.pop().slide; 603 var nextIndex = nextSlide.index; 604 var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev'; 605 clearBufferedTransitions(); 606 607 goNext(nextSlide, nextIndex, nextDirection); 608 } 609 } 610 }); 611 } 612 613 $scope.active = slide.index; 614 currentIndex = slide.index; 615 setActive(index); 616 617 //every time you change slides, reset the timer 618 restartTimer(); 619 } 620 621 function findSlideIndex(slide) { 622 for (var i = 0; i < slides.length; i++) { 623 if (slides[i].slide === slide) { 624 return i; 625 } 626 } 627 } 628 629 function resetTimer() { 630 if (currentInterval) { 631 $interval.cancel(currentInterval); 632 currentInterval = null; 633 } 634 } 635 636 function resetTransition(slides) { 637 if (!slides.length) { 638 $scope.$currentTransition = null; 639 clearBufferedTransitions(); 640 } 641 } 642 643 function restartTimer() { 644 resetTimer(); 645 var interval = +$scope.interval; 646 if (!isNaN(interval) && interval > 0) { 647 currentInterval = $interval(timerFn, interval); 648 } 649 } 650 651 function timerFn() { 652 var interval = +$scope.interval; 653 if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) { 654 $scope.next(); 655 } else { 656 $scope.pause(); 657 } 658 } 659}]) 660 661.directive('uibCarousel', function() { 662 return { 663 transclude: true, 664 replace: true, 665 controller: 'UibCarouselController', 666 controllerAs: 'carousel', 667 templateUrl: function(element, attrs) { 668 return attrs.templateUrl || 'uib/template/carousel/carousel.html'; 669 }, 670 scope: { 671 active: '=', 672 interval: '=', 673 noTransition: '=', 674 noPause: '=', 675 noWrap: '&' 676 } 677 }; 678}) 679 680.directive('uibSlide', function() { 681 return { 682 require: '^uibCarousel', 683 transclude: true, 684 replace: true, 685 templateUrl: function(element, attrs) { 686 return attrs.templateUrl || 'uib/template/carousel/slide.html'; 687 }, 688 scope: { 689 actual: '=?', 690 index: '=?' 691 }, 692 link: function (scope, element, attrs, carouselCtrl) { 693 carouselCtrl.addSlide(scope, element); 694 //when the scope is destroyed then remove the slide from the current slides array 695 scope.$on('$destroy', function() { 696 carouselCtrl.removeSlide(scope); 697 }); 698 } 699 }; 700}) 701 702.animation('.item', ['$animateCss', 703function($animateCss) { 704 var SLIDE_DIRECTION = 'uib-slideDirection'; 705 706 function removeClass(element, className, callback) { 707 element.removeClass(className); 708 if (callback) { 709 callback(); 710 } 711 } 712 713 return { 714 beforeAddClass: function(element, className, done) { 715 if (className === 'active') { 716 var stopped = false; 717 var direction = element.data(SLIDE_DIRECTION); 718 var directionClass = direction === 'next' ? 'left' : 'right'; 719 var removeClassFn = removeClass.bind(this, element, 720 directionClass + ' ' + direction, done); 721 element.addClass(direction); 722 723 $animateCss(element, {addClass: directionClass}) 724 .start() 725 .done(removeClassFn); 726 727 return function() { 728 stopped = true; 729 }; 730 } 731 done(); 732 }, 733 beforeRemoveClass: function (element, className, done) { 734 if (className === 'active') { 735 var stopped = false; 736 var direction = element.data(SLIDE_DIRECTION); 737 var directionClass = direction === 'next' ? 'left' : 'right'; 738 var removeClassFn = removeClass.bind(this, element, directionClass, done); 739 740 $animateCss(element, {addClass: directionClass}) 741 .start() 742 .done(removeClassFn); 743 744 return function() { 745 stopped = true; 746 }; 747 } 748 done(); 749 } 750 }; 751}]); 752 753angular.module('ui.bootstrap.dateparser', []) 754 755.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) { 756 // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js 757 var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; 758 759 var localeId; 760 var formatCodeToRegex; 761 762 this.init = function() { 763 localeId = $locale.id; 764 765 this.parsers = {}; 766 this.formatters = {}; 767 768 formatCodeToRegex = [ 769 { 770 key: 'yyyy', 771 regex: '\\d{4}', 772 apply: function(value) { this.year = +value; }, 773 formatter: function(date) { 774 var _date = new Date(); 775 _date.setFullYear(Math.abs(date.getFullYear())); 776 return dateFilter(_date, 'yyyy'); 777 } 778 }, 779 { 780 key: 'yy', 781 regex: '\\d{2}', 782 apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; }, 783 formatter: function(date) { 784 var _date = new Date(); 785 _date.setFullYear(Math.abs(date.getFullYear())); 786 return dateFilter(_date, 'yy'); 787 } 788 }, 789 { 790 key: 'y', 791 regex: '\\d{1,4}', 792 apply: function(value) { this.year = +value; }, 793 formatter: function(date) { 794 var _date = new Date(); 795 _date.setFullYear(Math.abs(date.getFullYear())); 796 return dateFilter(_date, 'y'); 797 } 798 }, 799 { 800 key: 'M!', 801 regex: '0?[1-9]|1[0-2]', 802 apply: function(value) { this.month = value - 1; }, 803 formatter: function(date) { 804 var value = date.getMonth(); 805 if (/^[0-9]$/.test(value)) { 806 return dateFilter(date, 'MM'); 807 } 808 809 return dateFilter(date, 'M'); 810 } 811 }, 812 { 813 key: 'MMMM', 814 regex: $locale.DATETIME_FORMATS.MONTH.join('|'), 815 apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }, 816 formatter: function(date) { return dateFilter(date, 'MMMM'); } 817 }, 818 { 819 key: 'MMM', 820 regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'), 821 apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }, 822 formatter: function(date) { return dateFilter(date, 'MMM'); } 823 }, 824 { 825 key: 'MM', 826 regex: '0[1-9]|1[0-2]', 827 apply: function(value) { this.month = value - 1; }, 828 formatter: function(date) { return dateFilter(date, 'MM'); } 829 }, 830 { 831 key: 'M', 832 regex: '[1-9]|1[0-2]', 833 apply: function(value) { this.month = value - 1; }, 834 formatter: function(date) { return dateFilter(date, 'M'); } 835 }, 836 { 837 key: 'd!', 838 regex: '[0-2]?[0-9]{1}|3[0-1]{1}', 839 apply: function(value) { this.date = +value; }, 840 formatter: function(date) { 841 var value = date.getDate(); 842 if (/^[1-9]$/.test(value)) { 843 return dateFilter(date, 'dd'); 844 } 845 846 return dateFilter(date, 'd'); 847 } 848 }, 849 { 850 key: 'dd', 851 regex: '[0-2][0-9]{1}|3[0-1]{1}', 852 apply: function(value) { this.date = +value; }, 853 formatter: function(date) { return dateFilter(date, 'dd'); } 854 }, 855 { 856 key: 'd', 857 regex: '[1-2]?[0-9]{1}|3[0-1]{1}', 858 apply: function(value) { this.date = +value; }, 859 formatter: function(date) { return dateFilter(date, 'd'); } 860 }, 861 { 862 key: 'EEEE', 863 regex: $locale.DATETIME_FORMATS.DAY.join('|'), 864 formatter: function(date) { return dateFilter(date, 'EEEE'); } 865 }, 866 { 867 key: 'EEE', 868 regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'), 869 formatter: function(date) { return dateFilter(date, 'EEE'); } 870 }, 871 { 872 key: 'HH', 873 regex: '(?:0|1)[0-9]|2[0-3]', 874 apply: function(value) { this.hours = +value; }, 875 formatter: function(date) { return dateFilter(date, 'HH'); } 876 }, 877 { 878 key: 'hh', 879 regex: '0[0-9]|1[0-2]', 880 apply: function(value) { this.hours = +value; }, 881 formatter: function(date) { return dateFilter(date, 'hh'); } 882 }, 883 { 884 key: 'H', 885 regex: '1?[0-9]|2[0-3]', 886 apply: function(value) { this.hours = +value; }, 887 formatter: function(date) { return dateFilter(date, 'H'); } 888 }, 889 { 890 key: 'h', 891 regex: '[0-9]|1[0-2]', 892 apply: function(value) { this.hours = +value; }, 893 formatter: function(date) { return dateFilter(date, 'h'); } 894 }, 895 { 896 key: 'mm', 897 regex: '[0-5][0-9]', 898 apply: function(value) { this.minutes = +value; }, 899 formatter: function(date) { return dateFilter(date, 'mm'); } 900 }, 901 { 902 key: 'm', 903 regex: '[0-9]|[1-5][0-9]', 904 apply: function(value) { this.minutes = +value; }, 905 formatter: function(date) { return dateFilter(date, 'm'); } 906 }, 907 { 908 key: 'sss', 909 regex: '[0-9][0-9][0-9]', 910 apply: function(value) { this.milliseconds = +value; }, 911 formatter: function(date) { return dateFilter(date, 'sss'); } 912 }, 913 { 914 key: 'ss', 915 regex: '[0-5][0-9]', 916 apply: function(value) { this.seconds = +value; }, 917 formatter: function(date) { return dateFilter(date, 'ss'); } 918 }, 919 { 920 key: 's', 921 regex: '[0-9]|[1-5][0-9]', 922 apply: function(value) { this.seconds = +value; }, 923 formatter: function(date) { return dateFilter(date, 's'); } 924 }, 925 { 926 key: 'a', 927 regex: $locale.DATETIME_FORMATS.AMPMS.join('|'), 928 apply: function(value) { 929 if (this.hours === 12) { 930 this.hours = 0; 931 } 932 933 if (value === 'PM') { 934 this.hours += 12; 935 } 936 }, 937 formatter: function(date) { return dateFilter(date, 'a'); } 938 }, 939 { 940 key: 'Z', 941 regex: '[+-]\\d{4}', 942 apply: function(value) { 943 var matches = value.match(/([+-])(\d{2})(\d{2})/), 944 sign = matches[1], 945 hours = matches[2], 946 minutes = matches[3]; 947 this.hours += toInt(sign + hours); 948 this.minutes += toInt(sign + minutes); 949 }, 950 formatter: function(date) { 951 return dateFilter(date, 'Z'); 952 } 953 }, 954 { 955 key: 'ww', 956 regex: '[0-4][0-9]|5[0-3]', 957 formatter: function(date) { return dateFilter(date, 'ww'); } 958 }, 959 { 960 key: 'w', 961 regex: '[0-9]|[1-4][0-9]|5[0-3]', 962 formatter: function(date) { return dateFilter(date, 'w'); } 963 }, 964 { 965 key: 'GGGG', 966 regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'), 967 formatter: function(date) { return dateFilter(date, 'GGGG'); } 968 }, 969 { 970 key: 'GGG', 971 regex: $locale.DATETIME_FORMATS.ERAS.join('|'), 972 formatter: function(date) { return dateFilter(date, 'GGG'); } 973 }, 974 { 975 key: 'GG', 976 regex: $locale.DATETIME_FORMATS.ERAS.join('|'), 977 formatter: function(date) { return dateFilter(date, 'GG'); } 978 }, 979 { 980 key: 'G', 981 regex: $locale.DATETIME_FORMATS.ERAS.join('|'), 982 formatter: function(date) { return dateFilter(date, 'G'); } 983 } 984 ]; 985 }; 986 987 this.init(); 988 989 function createParser(format, func) { 990 var map = [], regex = format.split(''); 991 992 // check for literal values 993 var quoteIndex = format.indexOf('\''); 994 if (quoteIndex > -1) { 995 var inLiteral = false; 996 format = format.split(''); 997 for (var i = quoteIndex; i < format.length; i++) { 998 if (inLiteral) { 999 if (format[i] === '\'') { 1000 if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote 1001 format[i+1] = '$'; 1002 regex[i+1] = ''; 1003 } else { // end of literal 1004 regex[i] = ''; 1005 inLiteral = false; 1006 } 1007 } 1008 format[i] = '$'; 1009 } else { 1010 if (format[i] === '\'') { // start of literal 1011 format[i] = '$'; 1012 regex[i] = ''; 1013 inLiteral = true; 1014 } 1015 } 1016 } 1017 1018 format = format.join(''); 1019 } 1020 1021 angular.forEach(formatCodeToRegex, function(data) { 1022 var index = format.indexOf(data.key); 1023 1024 if (index > -1) { 1025 format = format.split(''); 1026 1027 regex[index] = '(' + data.regex + ')'; 1028 format[index] = '$'; // Custom symbol to define consumed part of format 1029 for (var i = index + 1, n = index + data.key.length; i < n; i++) { 1030 regex[i] = ''; 1031 format[i] = '$'; 1032 } 1033 format = format.join(''); 1034 1035 map.push({ 1036 index: index, 1037 key: data.key, 1038 apply: data[func], 1039 matcher: data.regex 1040 }); 1041 } 1042 }); 1043 1044 return { 1045 regex: new RegExp('^' + regex.join('') + '$'), 1046 map: orderByFilter(map, 'index') 1047 }; 1048 } 1049 1050 this.filter = function(date, format) { 1051 if (!angular.isDate(date) || isNaN(date) || !format) { 1052 return ''; 1053 } 1054 1055 format = $locale.DATETIME_FORMATS[format] || format; 1056 1057 if ($locale.id !== localeId) { 1058 this.init(); 1059 } 1060 1061 if (!this.formatters[format]) { 1062 this.formatters[format] = createParser(format, 'formatter'); 1063 } 1064 1065 var parser = this.formatters[format], 1066 map = parser.map; 1067 1068 var _format = format; 1069 1070 return map.reduce(function(str, mapper, i) { 1071 var match = _format.match(new RegExp('(.*)' + mapper.key)); 1072 if (match && angular.isString(match[1])) { 1073 str += match[1]; 1074 _format = _format.replace(match[1] + mapper.key, ''); 1075 } 1076 1077 var endStr = i === map.length - 1 ? _format : ''; 1078 1079 if (mapper.apply) { 1080 return str + mapper.apply.call(null, date) + endStr; 1081 } 1082 1083 return str + endStr; 1084 }, ''); 1085 }; 1086 1087 this.parse = function(input, format, baseDate) { 1088 if (!angular.isString(input) || !format) { 1089 return input; 1090 } 1091 1092 format = $locale.DATETIME_FORMATS[format] || format; 1093 format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&'); 1094 1095 if ($locale.id !== localeId) { 1096 this.init(); 1097 } 1098 1099 if (!this.parsers[format]) { 1100 this.parsers[format] = createParser(format, 'apply'); 1101 } 1102 1103 var parser = this.parsers[format], 1104 regex = parser.regex, 1105 map = parser.map, 1106 results = input.match(regex), 1107 tzOffset = false; 1108 if (results && results.length) { 1109 var fields, dt; 1110 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) { 1111 fields = { 1112 year: baseDate.getFullYear(), 1113 month: baseDate.getMonth(), 1114 date: baseDate.getDate(), 1115 hours: baseDate.getHours(), 1116 minutes: baseDate.getMinutes(), 1117 seconds: baseDate.getSeconds(), 1118 milliseconds: baseDate.getMilliseconds() 1119 }; 1120 } else { 1121 if (baseDate) { 1122 $log.warn('dateparser:', 'baseDate is not a valid date'); 1123 } 1124 fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 }; 1125 } 1126 1127 for (var i = 1, n = results.length; i < n; i++) { 1128 var mapper = map[i - 1]; 1129 if (mapper.matcher === 'Z') { 1130 tzOffset = true; 1131 } 1132 1133 if (mapper.apply) { 1134 mapper.apply.call(fields, results[i]); 1135 } 1136 } 1137 1138 var datesetter = tzOffset ? Date.prototype.setUTCFullYear : 1139 Date.prototype.setFullYear; 1140 var timesetter = tzOffset ? Date.prototype.setUTCHours : 1141 Date.prototype.setHours; 1142 1143 if (isValid(fields.year, fields.month, fields.date)) { 1144 if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) { 1145 dt = new Date(baseDate); 1146 datesetter.call(dt, fields.year, fields.month, fields.date); 1147 timesetter.call(dt, fields.hours, fields.minutes, 1148 fields.seconds, fields.milliseconds); 1149 } else { 1150 dt = new Date(0); 1151 datesetter.call(dt, fields.year, fields.month, fields.date); 1152 timesetter.call(dt, fields.hours || 0, fields.minutes || 0, 1153 fields.seconds || 0, fields.milliseconds || 0); 1154 } 1155 } 1156 1157 return dt; 1158 } 1159 }; 1160 1161 // Check if date is valid for specific month (and year for February). 1162 // Month: 0 = Jan, 1 = Feb, etc 1163 function isValid(year, month, date) { 1164 if (date < 1) { 1165 return false; 1166 } 1167 1168 if (month === 1 && date > 28) { 1169 return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0); 1170 } 1171 1172 if (month === 3 || month === 5 || month === 8 || month === 10) { 1173 return date < 31; 1174 } 1175 1176 return true; 1177 } 1178 1179 function toInt(str) { 1180 return parseInt(str, 10); 1181 } 1182 1183 this.toTimezone = toTimezone; 1184 this.fromTimezone = fromTimezone; 1185 this.timezoneToOffset = timezoneToOffset; 1186 this.addDateMinutes = addDateMinutes; 1187 this.convertTimezoneToLocal = convertTimezoneToLocal; 1188 1189 function toTimezone(date, timezone) { 1190 return date && timezone ? convertTimezoneToLocal(date, timezone) : date; 1191 } 1192 1193 function fromTimezone(date, timezone) { 1194 return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date; 1195 } 1196 1197 //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 1198 function timezoneToOffset(timezone, fallback) { 1199 timezone = timezone.replace(/:/g, ''); 1200 var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 1201 return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 1202 } 1203 1204 function addDateMinutes(date, minutes) { 1205 date = new Date(date.getTime()); 1206 date.setMinutes(date.getMinutes() + minutes); 1207 return date; 1208 } 1209 1210 function convertTimezoneToLocal(date, timezone, reverse) { 1211 reverse = reverse ? -1 : 1; 1212 var dateTimezoneOffset = date.getTimezoneOffset(); 1213 var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 1214 return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 1215 } 1216}]); 1217 1218// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to 1219// at most one element. 1220angular.module('ui.bootstrap.isClass', []) 1221.directive('uibIsClass', [ 1222 '$animate', 1223function ($animate) { 1224 // 11111111 22222222 1225 var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/; 1226 // 11111111 22222222 1227 var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/; 1228 1229 var dataPerTracked = {}; 1230 1231 return { 1232 restrict: 'A', 1233 compile: function(tElement, tAttrs) { 1234 var linkedScopes = []; 1235 var instances = []; 1236 var expToData = {}; 1237 var lastActivated = null; 1238 var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP); 1239 var onExp = onExpMatches[2]; 1240 var expsStr = onExpMatches[1]; 1241 var exps = expsStr.split(','); 1242 1243 return linkFn; 1244 1245 function linkFn(scope, element, attrs) { 1246 linkedScopes.push(scope); 1247 instances.push({ 1248 scope: scope, 1249 element: element 1250 }); 1251 1252 exps.forEach(function(exp, k) { 1253 addForExp(exp, scope); 1254 }); 1255 1256 scope.$on('$destroy', removeScope); 1257 } 1258 1259 function addForExp(exp, scope) { 1260 var matches = exp.match(IS_REGEXP); 1261 var clazz = scope.$eval(matches[1]); 1262 var compareWithExp = matches[2]; 1263 var data = expToData[exp]; 1264 if (!data) { 1265 var watchFn = function(compareWithVal) { 1266 var newActivated = null; 1267 instances.some(function(instance) { 1268 var thisVal = instance.scope.$eval(onExp); 1269 if (thisVal === compareWithVal) { 1270 newActivated = instance; 1271 return true; 1272 } 1273 }); 1274 if (data.lastActivated !== newActivated) { 1275 if (data.lastActivated) { 1276 $animate.removeClass(data.lastActivated.element, clazz); 1277 } 1278 if (newActivated) { 1279 $animate.addClass(newActivated.element, clazz); 1280 } 1281 data.lastActivated = newActivated; 1282 } 1283 }; 1284 expToData[exp] = data = { 1285 lastActivated: null, 1286 scope: scope, 1287 watchFn: watchFn, 1288 compareWithExp: compareWithExp, 1289 watcher: scope.$watch(compareWithExp, watchFn) 1290 }; 1291 } 1292 data.watchFn(scope.$eval(compareWithExp)); 1293 } 1294 1295 function removeScope(e) { 1296 var removedScope = e.targetScope; 1297 var index = linkedScopes.indexOf(removedScope); 1298 linkedScopes.splice(index, 1); 1299 instances.splice(index, 1); 1300 if (linkedScopes.length) { 1301 var newWatchScope = linkedScopes[0]; 1302 angular.forEach(expToData, function(data) { 1303 if (data.scope === removedScope) { 1304 data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn); 1305 data.scope = newWatchScope; 1306 } 1307 }); 1308 } else { 1309 expToData = {}; 1310 } 1311 } 1312 } 1313 }; 1314}]); 1315angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass']) 1316 1317.value('$datepickerSuppressError', false) 1318 1319.value('$datepickerLiteralWarning', true) 1320 1321.constant('uibDatepickerConfig', { 1322 datepickerMode: 'day', 1323 formatDay: 'dd', 1324 formatMonth: 'MMMM', 1325 formatYear: 'yyyy', 1326 formatDayHeader: 'EEE', 1327 formatDayTitle: 'MMMM yyyy', 1328 formatMonthTitle: 'yyyy', 1329 maxDate: null, 1330 maxMode: 'year', 1331 minDate: null, 1332 minMode: 'day', 1333 ngModelOptions: {}, 1334 shortcutPropagation: false, 1335 showWeeks: true, 1336 yearColumns: 5, 1337 yearRows: 4 1338}) 1339 1340.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser', 1341 function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) { 1342 var self = this, 1343 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl; 1344 ngModelOptions = {}, 1345 watchListeners = [], 1346 optionsUsed = !!$attrs.datepickerOptions; 1347 1348 if (!$scope.datepickerOptions) { 1349 $scope.datepickerOptions = {}; 1350 } 1351 1352 // Modes chain 1353 this.modes = ['day', 'month', 'year']; 1354 1355 [ 1356 'customClass', 1357 'dateDisabled', 1358 'datepickerMode', 1359 'formatDay', 1360 'formatDayHeader', 1361 'formatDayTitle', 1362 'formatMonth', 1363 'formatMonthTitle', 1364 'formatYear', 1365 'maxDate', 1366 'maxMode', 1367 'minDate', 1368 'minMode', 1369 'showWeeks', 1370 'shortcutPropagation', 1371 'startingDay', 1372 'yearColumns', 1373 'yearRows' 1374 ].forEach(function(key) { 1375 switch (key) { 1376 case 'customClass': 1377 case 'dateDisabled': 1378 $scope[key] = $scope.datepickerOptions[key] || angular.noop; 1379 break; 1380 case 'datepickerMode': 1381 $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ? 1382 $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode; 1383 break; 1384 case 'formatDay': 1385 case 'formatDayHeader': 1386 case 'formatDayTitle': 1387 case 'formatMonth': 1388 case 'formatMonthTitle': 1389 case 'formatYear': 1390 self[key] = angular.isDefined($scope.datepickerOptions[key]) ? 1391 $interpolate($scope.datepickerOptions[key])($scope.$parent) : 1392 datepickerConfig[key]; 1393 break; 1394 case 'showWeeks': 1395 case 'shortcutPropagation': 1396 case 'yearColumns': 1397 case 'yearRows': 1398 self[key] = angular.isDefined($scope.datepickerOptions[key]) ? 1399 $scope.datepickerOptions[key] : datepickerConfig[key]; 1400 break; 1401 case 'startingDay': 1402 if (angular.isDefined($scope.datepickerOptions.startingDay)) { 1403 self.startingDay = $scope.datepickerOptions.startingDay; 1404 } else if (angular.isNumber(datepickerConfig.startingDay)) { 1405 self.startingDay = datepickerConfig.startingDay; 1406 } else { 1407 self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7; 1408 } 1409 1410 break; 1411 case 'maxDate': 1412 case 'minDate': 1413 $scope.$watch('datepickerOptions.' + key, function(value) { 1414 if (value) { 1415 if (angular.isDate(value)) { 1416 self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone); 1417 } else { 1418 if ($datepickerLiteralWarning) { 1419 $log.warn('Literal date support has been deprecated, please switch to date object usage'); 1420 } 1421 1422 self[key] = new Date(dateFilter(value, 'medium')); 1423 } 1424 } else { 1425 self[key] = datepickerConfig[key] ? 1426 dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) : 1427 null; 1428 } 1429 1430 self.refreshView(); 1431 }); 1432 1433 break; 1434 case 'maxMode': 1435 case 'minMode': 1436 if ($scope.datepickerOptions[key]) { 1437 $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) { 1438 self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key]; 1439 if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) || 1440 key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) { 1441 $scope.datepickerMode = self[key]; 1442 $scope.datepickerOptions.datepickerMode = self[key]; 1443 } 1444 }); 1445 } else { 1446 self[key] = $scope[key] = datepickerConfig[key] || null; 1447 } 1448 1449 break; 1450 } 1451 }); 1452 1453 $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000); 1454 1455 $scope.disabled = angular.isDefined($attrs.disabled) || false; 1456 if (angular.isDefined($attrs.ngDisabled)) { 1457 watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) { 1458 $scope.disabled = disabled; 1459 self.refreshView(); 1460 })); 1461 } 1462 1463 $scope.isActive = function(dateObject) { 1464 if (self.compare(dateObject.date, self.activeDate) === 0) { 1465 $scope.activeDateId = dateObject.uid; 1466 return true; 1467 } 1468 return false; 1469 }; 1470 1471 this.init = function(ngModelCtrl_) { 1472 ngModelCtrl = ngModelCtrl_; 1473 ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions; 1474 if ($scope.datepickerOptions.initDate) { 1475 self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date(); 1476 $scope.$watch('datepickerOptions.initDate', function(initDate) { 1477 if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) { 1478 self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone); 1479 self.refreshView(); 1480 } 1481 }); 1482 } else { 1483 self.activeDate = new Date(); 1484 } 1485 1486 var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : new Date(); 1487 this.activeDate = !isNaN(date) ? 1488 dateParser.fromTimezone(date, ngModelOptions.timezone) : 1489 dateParser.fromTimezone(new Date(), ngModelOptions.timezone); 1490 1491 ngModelCtrl.$render = function() { 1492 self.render(); 1493 }; 1494 }; 1495 1496 this.render = function() { 1497 if (ngModelCtrl.$viewValue) { 1498 var date = new Date(ngModelCtrl.$viewValue), 1499 isValid = !isNaN(date); 1500 1501 if (isValid) { 1502 this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone); 1503 } else if (!$datepickerSuppressError) { 1504 $log.error('Datepicker directive: "ng-model" value must be a Date object'); 1505 } 1506 } 1507 this.refreshView(); 1508 }; 1509 1510 this.refreshView = function() { 1511 if (this.element) { 1512 $scope.selectedDt = null; 1513 this._refreshView(); 1514 if ($scope.activeDt) { 1515 $scope.activeDateId = $scope.activeDt.uid; 1516 } 1517 1518 var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; 1519 date = dateParser.fromTimezone(date, ngModelOptions.timezone); 1520 ngModelCtrl.$setValidity('dateDisabled', !date || 1521 this.element && !this.isDisabled(date)); 1522 } 1523 }; 1524 1525 this.createDateObject = function(date, format) { 1526 var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null; 1527 model = dateParser.fromTimezone(model, ngModelOptions.timezone); 1528 var today = new Date(); 1529 today = dateParser.fromTimezone(today, ngModelOptions.timezone); 1530 var time = this.compare(date, today); 1531 var dt = { 1532 date: date, 1533 label: dateParser.filter(date, format), 1534 selected: model && this.compare(date, model) === 0, 1535 disabled: this.isDisabled(date), 1536 past: time < 0, 1537 current: time === 0, 1538 future: time > 0, 1539 customClass: this.customClass(date) || null 1540 }; 1541 1542 if (model && this.compare(date, model) === 0) { 1543 $scope.selectedDt = dt; 1544 } 1545 1546 if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) { 1547 $scope.activeDt = dt; 1548 } 1549 1550 return dt; 1551 }; 1552 1553 this.isDisabled = function(date) { 1554 return $scope.disabled || 1555 this.minDate && this.compare(date, this.minDate) < 0 || 1556 this.maxDate && this.compare(date, this.maxDate) > 0 || 1557 $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode}); 1558 }; 1559 1560 this.customClass = function(date) { 1561 return $scope.customClass({date: date, mode: $scope.datepickerMode}); 1562 }; 1563 1564 // Split array into smaller arrays 1565 this.split = function(arr, size) { 1566 var arrays = []; 1567 while (arr.length > 0) { 1568 arrays.push(arr.splice(0, size)); 1569 } 1570 return arrays; 1571 }; 1572 1573 $scope.select = function(date) { 1574 if ($scope.datepickerMode === self.minMode) { 1575 var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0); 1576 dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate()); 1577 dt = dateParser.toTimezone(dt, ngModelOptions.timezone); 1578 ngModelCtrl.$setViewValue(dt); 1579 ngModelCtrl.$render(); 1580 } else { 1581 self.activeDate = date; 1582 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]); 1583 1584 $scope.$emit('uib:datepicker.mode'); 1585 } 1586 1587 $scope.$broadcast('uib:datepicker.focus'); 1588 }; 1589 1590 $scope.move = function(direction) { 1591 var year = self.activeDate.getFullYear() + direction * (self.step.years || 0), 1592 month = self.activeDate.getMonth() + direction * (self.step.months || 0); 1593 self.activeDate.setFullYear(year, month, 1); 1594 self.refreshView(); 1595 }; 1596 1597 $scope.toggleMode = function(direction) { 1598 direction = direction || 1; 1599 1600 if ($scope.datepickerMode === self.maxMode && direction === 1 || 1601 $scope.datepickerMode === self.minMode && direction === -1) { 1602 return; 1603 } 1604 1605 setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]); 1606 1607 $scope.$emit('uib:datepicker.mode'); 1608 }; 1609 1610 // Key event mapper 1611 $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' }; 1612 1613 var focusElement = function() { 1614 self.element[0].focus(); 1615 }; 1616 1617 // Listen for focus requests from popup directive 1618 $scope.$on('uib:datepicker.focus', focusElement); 1619 1620 $scope.keydown = function(evt) { 1621 var key = $scope.keys[evt.which]; 1622 1623 if (!key || evt.shiftKey || evt.altKey || $scope.disabled) { 1624 return; 1625 } 1626 1627 evt.preventDefault(); 1628 if (!self.shortcutPropagation) { 1629 evt.stopPropagation(); 1630 } 1631 1632 if (key === 'enter' || key === 'space') { 1633 if (self.isDisabled(self.activeDate)) { 1634 return; // do nothing 1635 } 1636 $scope.select(self.activeDate); 1637 } else if (evt.ctrlKey && (key === 'up' || key === 'down')) { 1638 $scope.toggleMode(key === 'up' ? 1 : -1); 1639 } else { 1640 self.handleKeyDown(key, evt); 1641 self.refreshView(); 1642 } 1643 }; 1644 1645 $scope.$on('$destroy', function() { 1646 //Clear all watch listeners on destroy 1647 while (watchListeners.length) { 1648 watchListeners.shift()(); 1649 } 1650 }); 1651 1652 function setMode(mode) { 1653 $scope.datepickerMode = mode; 1654 $scope.datepickerOptions.datepickerMode = mode; 1655 } 1656}]) 1657 1658.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { 1659 var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; 1660 1661 this.step = { months: 1 }; 1662 this.element = $element; 1663 function getDaysInMonth(year, month) { 1664 return month === 1 && year % 4 === 0 && 1665 (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month]; 1666 } 1667 1668 this.init = function(ctrl) { 1669 angular.extend(ctrl, this); 1670 scope.showWeeks = ctrl.showWeeks; 1671 ctrl.refreshView(); 1672 }; 1673 1674 this.getDates = function(startDate, n) { 1675 var dates = new Array(n), current = new Date(startDate), i = 0, date; 1676 while (i < n) { 1677 date = new Date(current); 1678 dates[i++] = date; 1679 current.setDate(current.getDate() + 1); 1680 } 1681 return dates; 1682 }; 1683 1684 this._refreshView = function() { 1685 var year = this.activeDate.getFullYear(), 1686 month = this.activeDate.getMonth(), 1687 firstDayOfMonth = new Date(this.activeDate); 1688 1689 firstDayOfMonth.setFullYear(year, month, 1); 1690 1691 var difference = this.startingDay - firstDayOfMonth.getDay(), 1692 numDisplayedFromPreviousMonth = difference > 0 ? 1693 7 - difference : - difference, 1694 firstDate = new Date(firstDayOfMonth); 1695 1696 if (numDisplayedFromPreviousMonth > 0) { 1697 firstDate.setDate(-numDisplayedFromPreviousMonth + 1); 1698 } 1699 1700 // 42 is the number of days on a six-week calendar 1701 var days = this.getDates(firstDate, 42); 1702 for (var i = 0; i < 42; i ++) { 1703 days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), { 1704 secondary: days[i].getMonth() !== month, 1705 uid: scope.uniqueId + '-' + i 1706 }); 1707 } 1708 1709 scope.labels = new Array(7); 1710 for (var j = 0; j < 7; j++) { 1711 scope.labels[j] = { 1712 abbr: dateFilter(days[j].date, this.formatDayHeader), 1713 full: dateFilter(days[j].date, 'EEEE') 1714 }; 1715 } 1716 1717 scope.title = dateFilter(this.activeDate, this.formatDayTitle); 1718 scope.rows = this.split(days, 7); 1719 1720 if (scope.showWeeks) { 1721 scope.weekNumbers = []; 1722 var thursdayIndex = (4 + 7 - this.startingDay) % 7, 1723 numWeeks = scope.rows.length; 1724 for (var curWeek = 0; curWeek < numWeeks; curWeek++) { 1725 scope.weekNumbers.push( 1726 getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date)); 1727 } 1728 } 1729 }; 1730 1731 this.compare = function(date1, date2) { 1732 var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()); 1733 var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); 1734 _date1.setFullYear(date1.getFullYear()); 1735 _date2.setFullYear(date2.getFullYear()); 1736 return _date1 - _date2; 1737 }; 1738 1739 function getISO8601WeekNumber(date) { 1740 var checkDate = new Date(date); 1741 checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday 1742 var time = checkDate.getTime(); 1743 checkDate.setMonth(0); // Compare with Jan 1 1744 checkDate.setDate(1); 1745 return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1; 1746 } 1747 1748 this.handleKeyDown = function(key, evt) { 1749 var date = this.activeDate.getDate(); 1750 1751 if (key === 'left') { 1752 date = date - 1; 1753 } else if (key === 'up') { 1754 date = date - 7; 1755 } else if (key === 'right') { 1756 date = date + 1; 1757 } else if (key === 'down') { 1758 date = date + 7; 1759 } else if (key === 'pageup' || key === 'pagedown') { 1760 var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1); 1761 this.activeDate.setMonth(month, 1); 1762 date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date); 1763 } else if (key === 'home') { 1764 date = 1; 1765 } else if (key === 'end') { 1766 date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()); 1767 } 1768 this.activeDate.setDate(date); 1769 }; 1770}]) 1771 1772.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { 1773 this.step = { years: 1 }; 1774 this.element = $element; 1775 1776 this.init = function(ctrl) { 1777 angular.extend(ctrl, this); 1778 ctrl.refreshView(); 1779 }; 1780 1781 this._refreshView = function() { 1782 var months = new Array(12), 1783 year = this.activeDate.getFullYear(), 1784 date; 1785 1786 for (var i = 0; i < 12; i++) { 1787 date = new Date(this.activeDate); 1788 date.setFullYear(year, i, 1); 1789 months[i] = angular.extend(this.createDateObject(date, this.formatMonth), { 1790 uid: scope.uniqueId + '-' + i 1791 }); 1792 } 1793 1794 scope.title = dateFilter(this.activeDate, this.formatMonthTitle); 1795 scope.rows = this.split(months, 3); 1796 }; 1797 1798 this.compare = function(date1, date2) { 1799 var _date1 = new Date(date1.getFullYear(), date1.getMonth()); 1800 var _date2 = new Date(date2.getFullYear(), date2.getMonth()); 1801 _date1.setFullYear(date1.getFullYear()); 1802 _date2.setFullYear(date2.getFullYear()); 1803 return _date1 - _date2; 1804 }; 1805 1806 this.handleKeyDown = function(key, evt) { 1807 var date = this.activeDate.getMonth(); 1808 1809 if (key === 'left') { 1810 date = date - 1; 1811 } else if (key === 'up') { 1812 date = date - 3; 1813 } else if (key === 'right') { 1814 date = date + 1; 1815 } else if (key === 'down') { 1816 date = date + 3; 1817 } else if (key === 'pageup' || key === 'pagedown') { 1818 var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1); 1819 this.activeDate.setFullYear(year); 1820 } else if (key === 'home') { 1821 date = 0; 1822 } else if (key === 'end') { 1823 date = 11; 1824 } 1825 this.activeDate.setMonth(date); 1826 }; 1827}]) 1828 1829.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) { 1830 var columns, range; 1831 this.element = $element; 1832 1833 function getStartingYear(year) { 1834 return parseInt((year - 1) / range, 10) * range + 1; 1835 } 1836 1837 this.yearpickerInit = function() { 1838 columns = this.yearColumns; 1839 range = this.yearRows * columns; 1840 this.step = { years: range }; 1841 }; 1842 1843 this._refreshView = function() { 1844 var years = new Array(range), date; 1845 1846 for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) { 1847 date = new Date(this.activeDate); 1848 date.setFullYear(start + i, 0, 1); 1849 years[i] = angular.extend(this.createDateObject(date, this.formatYear), { 1850 uid: scope.uniqueId + '-' + i 1851 }); 1852 } 1853 1854 scope.title = [years[0].label, years[range - 1].label].join(' - '); 1855 scope.rows = this.split(years, columns); 1856 scope.columns = columns; 1857 }; 1858 1859 this.compare = function(date1, date2) { 1860 return date1.getFullYear() - date2.getFullYear(); 1861 }; 1862 1863 this.handleKeyDown = function(key, evt) { 1864 var date = this.activeDate.getFullYear(); 1865 1866 if (key === 'left') { 1867 date = date - 1; 1868 } else if (key === 'up') { 1869 date = date - columns; 1870 } else if (key === 'right') { 1871 date = date + 1; 1872 } else if (key === 'down') { 1873 date = date + columns; 1874 } else if (key === 'pageup' || key === 'pagedown') { 1875 date += (key === 'pageup' ? - 1 : 1) * range; 1876 } else if (key === 'home') { 1877 date = getStartingYear(this.activeDate.getFullYear()); 1878 } else if (key === 'end') { 1879 date = getStartingYear(this.activeDate.getFullYear()) + range - 1; 1880 } 1881 this.activeDate.setFullYear(date); 1882 }; 1883}]) 1884 1885.directive('uibDatepicker', function() { 1886 return { 1887 replace: true, 1888 templateUrl: function(element, attrs) { 1889 return attrs.templateUrl || 'uib/template/datepicker/datepicker.html'; 1890 }, 1891 scope: { 1892 datepickerOptions: '=?' 1893 }, 1894 require: ['uibDatepicker', '^ngModel'], 1895 controller: 'UibDatepickerController', 1896 controllerAs: 'datepicker', 1897 link: function(scope, element, attrs, ctrls) { 1898 var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 1899 1900 datepickerCtrl.init(ngModelCtrl); 1901 } 1902 }; 1903}) 1904 1905.directive('uibDaypicker', function() { 1906 return { 1907 replace: true, 1908 templateUrl: function(element, attrs) { 1909 return attrs.templateUrl || 'uib/template/datepicker/day.html'; 1910 }, 1911 require: ['^uibDatepicker', 'uibDaypicker'], 1912 controller: 'UibDaypickerController', 1913 link: function(scope, element, attrs, ctrls) { 1914 var datepickerCtrl = ctrls[0], 1915 daypickerCtrl = ctrls[1]; 1916 1917 daypickerCtrl.init(datepickerCtrl); 1918 } 1919 }; 1920}) 1921 1922.directive('uibMonthpicker', function() { 1923 return { 1924 replace: true, 1925 templateUrl: function(element, attrs) { 1926 return attrs.templateUrl || 'uib/template/datepicker/month.html'; 1927 }, 1928 require: ['^uibDatepicker', 'uibMonthpicker'], 1929 controller: 'UibMonthpickerController', 1930 link: function(scope, element, attrs, ctrls) { 1931 var datepickerCtrl = ctrls[0], 1932 monthpickerCtrl = ctrls[1]; 1933 1934 monthpickerCtrl.init(datepickerCtrl); 1935 } 1936 }; 1937}) 1938 1939.directive('uibYearpicker', function() { 1940 return { 1941 replace: true, 1942 templateUrl: function(element, attrs) { 1943 return attrs.templateUrl || 'uib/template/datepicker/year.html'; 1944 }, 1945 require: ['^uibDatepicker', 'uibYearpicker'], 1946 controller: 'UibYearpickerController', 1947 link: function(scope, element, attrs, ctrls) { 1948 var ctrl = ctrls[0]; 1949 angular.extend(ctrl, ctrls[1]); 1950 ctrl.yearpickerInit(); 1951 1952 ctrl.refreshView(); 1953 } 1954 }; 1955}); 1956 1957angular.module('ui.bootstrap.position', []) 1958 1959/** 1960 * A set of utility methods for working with the DOM. 1961 * It is meant to be used where we need to absolute-position elements in 1962 * relation to another element (this is the case for tooltips, popovers, 1963 * typeahead suggestions etc.). 1964 */ 1965 .factory('$uibPosition', ['$document', '$window', function($document, $window) { 1966 /** 1967 * Used by scrollbarWidth() function to cache scrollbar's width. 1968 * Do not access this variable directly, use scrollbarWidth() instead. 1969 */ 1970 var SCROLLBAR_WIDTH; 1971 /** 1972 * scrollbar on body and html element in IE and Edge overlay 1973 * content and should be considered 0 width. 1974 */ 1975 var BODY_SCROLLBAR_WIDTH; 1976 var OVERFLOW_REGEX = { 1977 normal: /(auto|scroll)/, 1978 hidden: /(auto|scroll|hidden)/ 1979 }; 1980 var PLACEMENT_REGEX = { 1981 auto: /\s?auto?\s?/i, 1982 primary: /^(top|bottom|left|right)$/, 1983 secondary: /^(top|bottom|left|right|center)$/, 1984 vertical: /^(top|bottom)$/ 1985 }; 1986 var BODY_REGEX = /(HTML|BODY)/; 1987 1988 return { 1989 1990 /** 1991 * Provides a raw DOM element from a jQuery/jQLite element. 1992 * 1993 * @param {element} elem - The element to convert. 1994 * 1995 * @returns {element} A HTML element. 1996 */ 1997 getRawNode: function(elem) { 1998 return elem.nodeName ? elem : elem[0] || elem; 1999 }, 2000 2001 /** 2002 * Provides a parsed number for a style property. Strips 2003 * units and casts invalid numbers to 0. 2004 * 2005 * @param {string} value - The style value to parse. 2006 * 2007 * @returns {number} A valid number. 2008 */ 2009 parseStyle: function(value) { 2010 value = parseFloat(value); 2011 return isFinite(value) ? value : 0; 2012 }, 2013 2014 /** 2015 * Provides the closest positioned ancestor. 2016 * 2017 * @param {element} element - The element to get the offest parent for. 2018 * 2019 * @returns {element} The closest positioned ancestor. 2020 */ 2021 offsetParent: function(elem) { 2022 elem = this.getRawNode(elem); 2023 2024 var offsetParent = elem.offsetParent || $document[0].documentElement; 2025 2026 function isStaticPositioned(el) { 2027 return ($window.getComputedStyle(el).position || 'static') === 'static'; 2028 } 2029 2030 while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) { 2031 offsetParent = offsetParent.offsetParent; 2032 } 2033 2034 return offsetParent || $document[0].documentElement; 2035 }, 2036 2037 /** 2038 * Provides the scrollbar width, concept from TWBS measureScrollbar() 2039 * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js 2040 * In IE and Edge, scollbar on body and html element overlay and should 2041 * return a width of 0. 2042 * 2043 * @returns {number} The width of the browser scollbar. 2044 */ 2045 scrollbarWidth: function(isBody) { 2046 if (isBody) { 2047 if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) { 2048 var bodyElem = $document.find('body'); 2049 bodyElem.addClass('uib-position-body-scrollbar-measure'); 2050 BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth; 2051 BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0; 2052 bodyElem.removeClass('uib-position-body-scrollbar-measure'); 2053 } 2054 return BODY_SCROLLBAR_WIDTH; 2055 } 2056 2057 if (angular.isUndefined(SCROLLBAR_WIDTH)) { 2058 var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>'); 2059 $document.find('body').append(scrollElem); 2060 SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth; 2061 SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0; 2062 scrollElem.remove(); 2063 } 2064 2065 return SCROLLBAR_WIDTH; 2066 }, 2067 2068 /** 2069 * Provides the padding required on an element to replace the scrollbar. 2070 * 2071 * @returns {object} An object with the following properties: 2072 * <ul> 2073 * <li>**scrollbarWidth**: the width of the scrollbar</li> 2074 * <li>**widthOverflow**: whether the the width is overflowing</li> 2075 * <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li> 2076 * <li>**rightOriginal**: the amount of right padding currently on the element</li> 2077 * <li>**heightOverflow**: whether the the height is overflowing</li> 2078 * <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li> 2079 * <li>**bottomOriginal**: the amount of bottom padding currently on the element</li> 2080 * </ul> 2081 */ 2082 scrollbarPadding: function(elem) { 2083 elem = this.getRawNode(elem); 2084 2085 var elemStyle = $window.getComputedStyle(elem); 2086 var paddingRight = this.parseStyle(elemStyle.paddingRight); 2087 var paddingBottom = this.parseStyle(elemStyle.paddingBottom); 2088 var scrollParent = this.scrollParent(elem, false, true); 2089 var scrollbarWidth = this.scrollbarWidth(scrollParent,
vendor: 18,751 bytes, lines 2089-2488
2089BODY_REGEX.test(scrollParent.tagName)); 2090 2091 return { 2092 scrollbarWidth: scrollbarWidth, 2093 widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth, 2094 right: paddingRight + scrollbarWidth, 2095 originalRight: paddingRight, 2096 heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight, 2097 bottom: paddingBottom + scrollbarWidth, 2098 originalBottom: paddingBottom 2099 }; 2100 }, 2101 2102 /** 2103 * Checks to see if the element is scrollable. 2104 * 2105 * @param {element} elem - The element to check. 2106 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, 2107 * default is false. 2108 * 2109 * @returns {boolean} Whether the element is scrollable. 2110 */ 2111 isScrollable: function(elem, includeHidden) { 2112 elem = this.getRawNode(elem); 2113 2114 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; 2115 var elemStyle = $window.getComputedStyle(elem); 2116 return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX); 2117 }, 2118 2119 /** 2120 * Provides the closest scrollable ancestor. 2121 * A port of the jQuery UI scrollParent method: 2122 * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js 2123 * 2124 * @param {element} elem - The element to find the scroll parent of. 2125 * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered, 2126 * default is false. 2127 * @param {boolean=} [includeSelf=false] - Should the element being passed be 2128 * included in the scrollable llokup. 2129 * 2130 * @returns {element} A HTML element. 2131 */ 2132 scrollParent: function(elem, includeHidden, includeSelf) { 2133 elem = this.getRawNode(elem); 2134 2135 var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal; 2136 var documentEl = $document[0].documentElement; 2137 var elemStyle = $window.getComputedStyle(elem); 2138 if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) { 2139 return elem; 2140 } 2141 var excludeStatic = elemStyle.position === 'absolute'; 2142 var scrollParent = elem.parentElement || documentEl; 2143 2144 if (scrollParent === documentEl || elemStyle.position === 'fixed') { 2145 return documentEl; 2146 } 2147 2148 while (scrollParent.parentElement && scrollParent !== documentEl) { 2149 var spStyle = $window.getComputedStyle(scrollParent); 2150 if (excludeStatic && spStyle.position !== 'static') { 2151 excludeStatic = false; 2152 } 2153 2154 if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) { 2155 break; 2156 } 2157 scrollParent = scrollParent.parentElement; 2158 } 2159 2160 return scrollParent; 2161 }, 2162 2163 /** 2164 * Provides read-only equivalent of jQuery's position function: 2165 * http://api.jquery.com/position/ - distance to closest positioned 2166 * ancestor. Does not account for margins by default like jQuery position. 2167 * 2168 * @param {element} elem - The element to caclulate the position on. 2169 * @param {boolean=} [includeMargins=false] - Should margins be accounted 2170 * for, default is false. 2171 * 2172 * @returns {object} An object with the following properties: 2173 * <ul> 2174 * <li>**width**: the width of the element</li> 2175 * <li>**height**: the height of the element</li> 2176 * <li>**top**: distance to top edge of offset parent</li> 2177 * <li>**left**: distance to left edge of offset parent</li> 2178 * </ul> 2179 */ 2180 position: function(elem, includeMagins) { 2181 elem = this.getRawNode(elem); 2182 2183 var elemOffset = this.offset(elem); 2184 if (includeMagins) { 2185 var elemStyle = $window.getComputedStyle(elem); 2186 elemOffset.top -= this.parseStyle(elemStyle.marginTop); 2187 elemOffset.left -= this.parseStyle(elemStyle.marginLeft); 2188 } 2189 var parent = this.offsetParent(elem); 2190 var parentOffset = {top: 0, left: 0}; 2191 2192 if (parent !== $document[0].documentElement) { 2193 parentOffset = this.offset(parent); 2194 parentOffset.top += parent.clientTop - parent.scrollTop; 2195 parentOffset.left += parent.clientLeft - parent.scrollLeft; 2196 } 2197 2198 return { 2199 width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth), 2200 height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight), 2201 top: Math.round(elemOffset.top - parentOffset.top), 2202 left: Math.round(elemOffset.left - parentOffset.left) 2203 }; 2204 }, 2205 2206 /** 2207 * Provides read-only equivalent of jQuery's offset function: 2208 * http://api.jquery.com/offset/ - distance to viewport. Does 2209 * not account for borders, margins, or padding on the body 2210 * element. 2211 * 2212 * @param {element} elem - The element to calculate the offset on. 2213 * 2214 * @returns {object} An object with the following properties: 2215 * <ul> 2216 * <li>**width**: the width of the element</li> 2217 * <li>**height**: the height of the element</li> 2218 * <li>**top**: distance to top edge of viewport</li> 2219 * <li>**right**: distance to bottom edge of viewport</li> 2220 * </ul> 2221 */ 2222 offset: function(elem) { 2223 elem = this.getRawNode(elem); 2224 2225 var elemBCR = elem.getBoundingClientRect(); 2226 return { 2227 width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth), 2228 height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight), 2229 top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)), 2230 left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)) 2231 }; 2232 }, 2233 2234 /** 2235 * Provides offset distance to the closest scrollable ancestor 2236 * or viewport. Accounts for border and scrollbar width. 2237 * 2238 * Right and bottom dimensions represent the distance to the 2239 * respective edge of the viewport element. If the element 2240 * edge extends beyond the viewport, a negative value will be 2241 * reported. 2242 * 2243 * @param {element} elem - The element to get the viewport offset for. 2244 * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead 2245 * of the first scrollable element, default is false. 2246 * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element 2247 * be accounted for, default is true. 2248 * 2249 * @returns {object} An object with the following properties: 2250 * <ul> 2251 * <li>**top**: distance to the top content edge of viewport element</li> 2252 * <li>**bottom**: distance to the bottom content edge of viewport element</li> 2253 * <li>**left**: distance to the left content edge of viewport element</li> 2254 * <li>**right**: distance to the right content edge of viewport element</li> 2255 * </ul> 2256 */ 2257 viewportOffset: function(elem, useDocument, includePadding) { 2258 elem = this.getRawNode(elem); 2259 includePadding = includePadding !== false ? true : false; 2260 2261 var elemBCR = elem.getBoundingClientRect(); 2262 var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0}; 2263 2264 var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem); 2265 var offsetParentBCR = offsetParent.getBoundingClientRect(); 2266 2267 offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop; 2268 offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft; 2269 if (offsetParent === $document[0].documentElement) { 2270 offsetBCR.top += $window.pageYOffset; 2271 offsetBCR.left += $window.pageXOffset; 2272 } 2273 offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight; 2274 offsetBCR.right = offsetBCR.left + offsetParent.clientWidth; 2275 2276 if (includePadding) { 2277 var offsetParentStyle = $window.getComputedStyle(offsetParent); 2278 offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop); 2279 offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom); 2280 offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft); 2281 offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight); 2282 } 2283 2284 return { 2285 top: Math.round(elemBCR.top - offsetBCR.top), 2286 bottom: Math.round(offsetBCR.bottom - elemBCR.bottom), 2287 left: Math.round(elemBCR.left - offsetBCR.left), 2288 right: Math.round(offsetBCR.right - elemBCR.right) 2289 }; 2290 }, 2291 2292 /** 2293 * Provides an array of placement values parsed from a placement string. 2294 * Along with the 'auto' indicator, supported placement strings are: 2295 * <ul> 2296 * <li>top: element on top, horizontally centered on host element.</li> 2297 * <li>top-left: element on top, left edge aligned with host element left edge.</li> 2298 * <li>top-right: element on top, lerightft edge aligned with host element right edge.</li> 2299 * <li>bottom: element on bottom, horizontally centered on host element.</li> 2300 * <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li> 2301 * <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li> 2302 * <li>left: element on left, vertically centered on host element.</li> 2303 * <li>left-top: element on left, top edge aligned with host element top edge.</li> 2304 * <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li> 2305 * <li>right: element on right, vertically centered on host element.</li> 2306 * <li>right-top: element on right, top edge aligned with host element top edge.</li> 2307 * <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li> 2308 * </ul> 2309 * A placement string with an 'auto' indicator is expected to be 2310 * space separated from the placement, i.e: 'auto bottom-left' If 2311 * the primary and secondary placement values do not match 'top, 2312 * bottom, left, right' then 'top' will be the primary placement and 2313 * 'center' will be the secondary placement. If 'auto' is passed, true 2314 * will be returned as the 3rd value of the array. 2315 * 2316 * @param {string} placement - The placement string to parse. 2317 * 2318 * @returns {array} An array with the following values 2319 * <ul> 2320 * <li>**[0]**: The primary placement.</li> 2321 * <li>**[1]**: The secondary placement.</li> 2322 * <li>**[2]**: If auto is passed: true, else undefined.</li> 2323 * </ul> 2324 */ 2325 parsePlacement: function(placement) { 2326 var autoPlace = PLACEMENT_REGEX.auto.test(placement); 2327 if (autoPlace) { 2328 placement = placement.replace(PLACEMENT_REGEX.auto, ''); 2329 } 2330 2331 placement = placement.split('-'); 2332 2333 placement[0] = placement[0] || 'top'; 2334 if (!PLACEMENT_REGEX.primary.test(placement[0])) { 2335 placement[0] = 'top'; 2336 } 2337 2338 placement[1] = placement[1] || 'center'; 2339 if (!PLACEMENT_REGEX.secondary.test(placement[1])) { 2340 placement[1] = 'center'; 2341 } 2342 2343 if (autoPlace) { 2344 placement[2] = true; 2345 } else { 2346 placement[2] = false; 2347 } 2348 2349 return placement; 2350 }, 2351 2352 /** 2353 * Provides coordinates for an element to be positioned relative to 2354 * another element. Passing 'auto' as part of the placement parameter 2355 * will enable smart placement - where the element fits. i.e: 2356 * 'auto left-top' will check to see if there is enough space to the left 2357 * of the hostElem to fit the targetElem, if not place right (same for secondary 2358 * top placement). Available space is calculated using the viewportOffset 2359 * function. 2360 * 2361 * @param {element} hostElem - The element to position against. 2362 * @param {element} targetElem - The element to position. 2363 * @param {string=} [placement=top] - The placement for the targetElem, 2364 * default is 'top'. 'center' is assumed as secondary placement for 2365 * 'top', 'left', 'right', and 'bottom' placements. Available placements are: 2366 * <ul> 2367 * <li>top</li> 2368 * <li>top-right</li> 2369 * <li>top-left</li> 2370 * <li>bottom</li> 2371 * <li>bottom-left</li> 2372 * <li>bottom-right</li> 2373 * <li>left</li> 2374 * <li>left-top</li> 2375 * <li>left-bottom</li> 2376 * <li>right</li> 2377 * <li>right-top</li> 2378 * <li>right-bottom</li> 2379 * </ul> 2380 * @param {boolean=} [appendToBody=false] - Should the top and left values returned 2381 * be calculated from the body element, default is false. 2382 * 2383 * @returns {object} An object with the following properties: 2384 * <ul> 2385 * <li>**top**: Value for targetElem top.</li> 2386 * <li>**left**: Value for targetElem left.</li> 2387 * <li>**placement**: The resolved placement.</li> 2388 * </ul> 2389 */ 2390 positionElements: function(hostElem, targetElem, placement, appendToBody) { 2391 hostElem = this.getRawNode(hostElem); 2392 targetElem = this.getRawNode(targetElem); 2393 2394 // need to read from prop to support tests. 2395 var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth'); 2396 var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight'); 2397 2398 placement = this.parsePlacement(placement); 2399 2400 var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem); 2401 var targetElemPos = {top: 0, left: 0, placement: ''}; 2402 2403 if (placement[2]) { 2404 var viewportOffset = this.viewportOffset(hostElem, appendToBody); 2405 2406 var targetElemStyle = $window.getComputedStyle(targetElem); 2407 var adjustedSize = { 2408 width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))), 2409 height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom))) 2410 }; 2411 2412 placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' : 2413 placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' : 2414 placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' : 2415 placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' : 2416 placement[0]; 2417 2418 placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' : 2419 placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' : 2420 placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' : 2421 placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' : 2422 placement[1]; 2423 2424 if (placement[1] === 'center') { 2425 if (PLACEMENT_REGEX.vertical.test(placement[0])) { 2426 var xOverflow = hostElemPos.width / 2 - targetWidth / 2; 2427 if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) { 2428 placement[1] = 'left'; 2429 } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) { 2430 placement[1] = 'right'; 2431 } 2432 } else { 2433 var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2; 2434 if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) { 2435 placement[1] = 'top'; 2436 } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) { 2437 placement[1] = 'bottom'; 2438 } 2439 } 2440 } 2441 } 2442 2443 switch (placement[0]) { 2444 case 'top': 2445 targetElemPos.top = hostElemPos.top - targetHeight; 2446 break; 2447 case 'bottom': 2448 targetElemPos.top = hostElemPos.top + hostElemPos.height; 2449 break; 2450 case 'left': 2451 targetElemPos.left = hostElemPos.left - targetWidth; 2452 break; 2453 case 'right': 2454 targetElemPos.left = hostElemPos.left + hostElemPos.width; 2455 break; 2456 } 2457 2458 switch (placement[1]) { 2459 case 'top': 2460 targetElemPos.top = hostElemPos.top; 2461 break; 2462 case 'bottom': 2463 targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight; 2464 break; 2465 case 'left': 2466 targetElemPos.left = hostElemPos.left; 2467 break; 2468 case 'right': 2469 targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth; 2470 break; 2471 case 'center': 2472 if (PLACEMENT_REGEX.vertical.test(placement[0])) { 2473 targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2; 2474 } else { 2475 targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2; 2476 } 2477 break; 2478 } 2479 2480 targetElemPos.top = Math.round(targetElemPos.top); 2481 targetElemPos.left = Math.round(targetElemPos.left); 2482 targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1]; 2483 2484 return targetElemPos; 2485 }, 2486 2487 /** 2488
vendor: 96,335 bytes, lines 2488-5439
2488* Provides a way for positioning tooltip & dropdown 2489 * arrows when using placement options beyond the standard 2490 * left, right, top, or bottom. 2491 * 2492 * @param {element} elem - The tooltip/dropdown element. 2493 * @param {string} placement - The placement for the elem. 2494 */ 2495 positionArrow: function(elem, placement) { 2496 elem = this.getRawNode(elem); 2497 2498 var innerElem = elem.querySelector('.tooltip-inner, .popover-inner'); 2499 if (!innerElem) { 2500 return; 2501 } 2502 2503 var isTooltip = angular.element(innerElem).hasClass('tooltip-inner'); 2504 2505 var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow'); 2506 if (!arrowElem) { 2507 return; 2508 } 2509 2510 var arrowCss = { 2511 top: '', 2512 bottom: '', 2513 left: '', 2514 right: '' 2515 }; 2516 2517 placement = this.parsePlacement(placement); 2518 if (placement[1] === 'center') { 2519 // no adjustment necessary - just reset styles 2520 angular.element(arrowElem).css(arrowCss); 2521 return; 2522 } 2523 2524 var borderProp = 'border-' + placement[0] + '-width'; 2525 var borderWidth = $window.getComputedStyle(arrowElem)[borderProp]; 2526 2527 var borderRadiusProp = 'border-'; 2528 if (PLACEMENT_REGEX.vertical.test(placement[0])) { 2529 borderRadiusProp += placement[0] + '-' + placement[1]; 2530 } else { 2531 borderRadiusProp += placement[1] + '-' + placement[0]; 2532 } 2533 borderRadiusProp += '-radius'; 2534 var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp]; 2535 2536 switch (placement[0]) { 2537 case 'top': 2538 arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth; 2539 break; 2540 case 'bottom': 2541 arrowCss.top = isTooltip ? '0' : '-' + borderWidth; 2542 break; 2543 case 'left': 2544 arrowCss.right = isTooltip ? '0' : '-' + borderWidth; 2545 break; 2546 case 'right': 2547 arrowCss.left = isTooltip ? '0' : '-' + borderWidth; 2548 break; 2549 } 2550 2551 arrowCss[placement[1]] = borderRadius; 2552 2553 angular.element(arrowElem).css(arrowCss); 2554 } 2555 }; 2556 }]); 2557 2558angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position']) 2559 2560.value('$datepickerPopupLiteralWarning', true) 2561 2562.constant('uibDatepickerPopupConfig', { 2563 altInputFormats: [], 2564 appendToBody: false, 2565 clearText: 'Clear', 2566 closeOnDateSelection: true, 2567 closeText: 'Done', 2568 currentText: 'Today', 2569 datepickerPopup: 'yyyy-MM-dd', 2570 datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html', 2571 datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html', 2572 html5Types: { 2573 date: 'yyyy-MM-dd', 2574 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss', 2575 'month': 'yyyy-MM' 2576 }, 2577 onOpenFocus: true, 2578 showButtonBar: true, 2579 placement: 'auto bottom-left' 2580}) 2581 2582.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', 2583function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { 2584 var cache = {}, 2585 isHtml5DateInput = false; 2586 var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, 2587 datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, 2588 ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [], 2589 timezone; 2590 2591 this.init = function(_ngModel_) { 2592 ngModel = _ngModel_; 2593 ngModelOptions = _ngModel_.$options; 2594 closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? 2595 $scope.$parent.$eval($attrs.closeOnDateSelection) : 2596 datepickerPopupConfig.closeOnDateSelection; 2597 appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? 2598 $scope.$parent.$eval($attrs.datepickerAppendToBody) : 2599 datepickerPopupConfig.appendToBody; 2600 onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? 2601 $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; 2602 datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? 2603 $attrs.datepickerPopupTemplateUrl : 2604 datepickerPopupConfig.datepickerPopupTemplateUrl; 2605 datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? 2606 $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; 2607 altInputFormats = angular.isDefined($attrs.altInputFormats) ? 2608 $scope.$parent.$eval($attrs.altInputFormats) : 2609 datepickerPopupConfig.altInputFormats; 2610 2611 $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? 2612 $scope.$parent.$eval($attrs.showButtonBar) : 2613 datepickerPopupConfig.showButtonBar; 2614 2615 if (datepickerPopupConfig.html5Types[$attrs.type]) { 2616 dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; 2617 isHtml5DateInput = true; 2618 } else { 2619 dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; 2620 $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { 2621 var newDateFormat = value || datepickerPopupConfig.datepickerPopup; 2622 // Invalidate the $modelValue to ensure that formatters re-run 2623 // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 2624 if (newDateFormat !== dateFormat) { 2625 dateFormat = newDateFormat; 2626 ngModel.$modelValue = null; 2627 2628 if (!dateFormat) { 2629 throw new Error('uibDatepickerPopup must have a date format specified.'); 2630 } 2631 } 2632 }); 2633 } 2634 2635 if (!dateFormat) { 2636 throw new Error('uibDatepickerPopup must have a date format specified.'); 2637 } 2638 2639 if (isHtml5DateInput && $attrs.uibDatepickerPopup) { 2640 throw new Error('HTML5 date input types do not support custom formats.'); 2641 } 2642 2643 // popup element used to display calendar 2644 popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>'); 2645 if (ngModelOptions) { 2646 timezone = ngModelOptions.timezone; 2647 $scope.ngModelOptions = angular.copy(ngModelOptions); 2648 $scope.ngModelOptions.timezone = null; 2649 if ($scope.ngModelOptions.updateOnDefault === true) { 2650 $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? 2651 $scope.ngModelOptions.updateOn + ' default' : 'default'; 2652 } 2653 2654 popupEl.attr('ng-model-options', 'ngModelOptions'); 2655 } else { 2656 timezone = null; 2657 } 2658 2659 popupEl.attr({ 2660 'ng-model': 'date', 2661 'ng-change': 'dateSelection(date)', 2662 'template-url': datepickerPopupTemplateUrl 2663 }); 2664 2665 // datepicker element 2666 datepickerEl = angular.element(popupEl.children()[0]); 2667 datepickerEl.attr('template-url', datepickerTemplateUrl); 2668 2669 if (!$scope.datepickerOptions) { 2670 $scope.datepickerOptions = {}; 2671 } 2672 2673 if (isHtml5DateInput) { 2674 if ($attrs.type === 'month') { 2675 $scope.datepickerOptions.datepickerMode = 'month'; 2676 $scope.datepickerOptions.minMode = 'month'; 2677 } 2678 } 2679 2680 datepickerEl.attr('datepicker-options', 'datepickerOptions'); 2681 2682 if (!isHtml5DateInput) { 2683 // Internal API to maintain the correct ng-invalid-[key] class 2684 ngModel.$$parserName = 'date'; 2685 ngModel.$validators.date = validator; 2686 ngModel.$parsers.unshift(parseDate); 2687 ngModel.$formatters.push(function(value) { 2688 if (ngModel.$isEmpty(value)) { 2689 $scope.date = value; 2690 return value; 2691 } 2692 2693 if (angular.isNumber(value)) { 2694 value = new Date(value); 2695 } 2696 2697 $scope.date = dateParser.fromTimezone(value, timezone); 2698 2699 return dateParser.filter($scope.date, dateFormat); 2700 }); 2701 } else { 2702 ngModel.$formatters.push(function(value) { 2703 $scope.date = dateParser.fromTimezone(value, timezone); 2704 return value; 2705 }); 2706 } 2707 2708 // Detect changes in the view from the text box 2709 ngModel.$viewChangeListeners.push(function() { 2710 $scope.date = parseDateString(ngModel.$viewValue); 2711 }); 2712 2713 $element.on('keydown', inputKeydownBind); 2714 2715 $popup = $compile(popupEl)($scope); 2716 // Prevent jQuery cache memory leak (template is now redundant after linking) 2717 popupEl.remove(); 2718 2719 if (appendToBody) { 2720 $document.find('body').append($popup); 2721 } else { 2722 $element.after($popup); 2723 } 2724 2725 $scope.$on('$destroy', function() { 2726 if ($scope.isOpen === true) { 2727 if (!$rootScope.$$phase) { 2728 $scope.$apply(function() { 2729 $scope.isOpen = false; 2730 }); 2731 } 2732 } 2733 2734 $popup.remove(); 2735 $element.off('keydown', inputKeydownBind); 2736 $document.off('click', documentClickBind); 2737 if (scrollParentEl) { 2738 scrollParentEl.off('scroll', positionPopup); 2739 } 2740 angular.element($window).off('resize', positionPopup); 2741 2742 //Clear all watch listeners on destroy 2743 while (watchListeners.length) { 2744 watchListeners.shift()(); 2745 } 2746 }); 2747 }; 2748 2749 $scope.getText = function(key) { 2750 return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; 2751 }; 2752 2753 $scope.isDisabled = function(date) { 2754 if (date === 'today') { 2755 date = dateParser.fromTimezone(new Date(), timezone); 2756 } 2757 2758 var dates = {}; 2759 angular.forEach(['minDate', 'maxDate'], function(key) { 2760 if (!$scope.datepickerOptions[key]) { 2761 dates[key] = null; 2762 } else if (angular.isDate($scope.datepickerOptions[key])) { 2763 dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone); 2764 } else { 2765 if ($datepickerPopupLiteralWarning) { 2766 $log.warn('Literal date support has been deprecated, please switch to date object usage'); 2767 } 2768 2769 dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); 2770 } 2771 }); 2772 2773 return $scope.datepickerOptions && 2774 dates.minDate && $scope.compare(date, dates.minDate) < 0 || 2775 dates.maxDate && $scope.compare(date, dates.maxDate) > 0; 2776 }; 2777 2778 $scope.compare = function(date1, date2) { 2779 return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); 2780 }; 2781 2782 // Inner change 2783 $scope.dateSelection = function(dt) { 2784 if (angular.isDefined(dt)) { 2785 $scope.date = dt; 2786 } 2787 var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function 2788 $element.val(date); 2789 ngModel.$setViewValue(date); 2790 2791 if (closeOnDateSelection) { 2792 $scope.isOpen = false; 2793 $element[0].focus(); 2794 } 2795 }; 2796 2797 $scope.keydown = function(evt) { 2798 if (evt.which === 27) { 2799 evt.stopPropagation(); 2800 $scope.isOpen = false; 2801 $element[0].focus(); 2802 } 2803 }; 2804 2805 $scope.select = function(date, evt) { 2806 evt.stopPropagation(); 2807 2808 if (date === 'today') { 2809 var today = new Date(); 2810 if (angular.isDate($scope.date)) { 2811 date = new Date($scope.date); 2812 date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); 2813 } else { 2814 date = new Date(today.setHours(0, 0, 0, 0)); 2815 } 2816 } 2817 $scope.dateSelection(date); 2818 }; 2819 2820 $scope.close = function(evt) { 2821 evt.stopPropagation(); 2822 2823 $scope.isOpen = false; 2824 $element[0].focus(); 2825 }; 2826 2827 $scope.disabled = angular.isDefined($attrs.disabled) || false; 2828 if ($attrs.ngDisabled) { 2829 watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { 2830 $scope.disabled = disabled; 2831 })); 2832 } 2833 2834 $scope.$watch('isOpen', function(value) { 2835 if (value) { 2836 if (!$scope.disabled) { 2837 $timeout(function() { 2838 positionPopup(); 2839 2840 if (onOpenFocus) { 2841 $scope.$broadcast('uib:datepicker.focus'); 2842 } 2843 2844 $document.on('click', documentClickBind); 2845 2846 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; 2847 if (appendToBody || $position.parsePlacement(placement)[2]) { 2848 scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); 2849 if (scrollParentEl) { 2850 scrollParentEl.on('scroll', positionPopup); 2851 } 2852 } else { 2853 scrollParentEl = null; 2854 } 2855 2856 angular.element($window).on('resize', positionPopup); 2857 }, 0, false); 2858 } else { 2859 $scope.isOpen = false; 2860 } 2861 } else { 2862 $document.off('click', documentClickBind); 2863 if (scrollParentEl) { 2864 scrollParentEl.off('scroll', positionPopup); 2865 } 2866 angular.element($window).off('resize', positionPopup); 2867 } 2868 }); 2869 2870 function cameltoDash(string) { 2871 return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); 2872 } 2873 2874 function parseDateString(viewValue) { 2875 var date = dateParser.parse(viewValue, dateFormat, $scope.date); 2876 if (isNaN(date)) { 2877 for (var i = 0; i < altInputFormats.length; i++) { 2878 date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); 2879 if (!isNaN(date)) { 2880 return date; 2881 } 2882 } 2883 } 2884 return date; 2885 } 2886 2887 function parseDate(viewValue) { 2888 if (angular.isNumber(viewValue)) { 2889 // presumably timestamp to date object 2890 viewValue = new Date(viewValue); 2891 } 2892 2893 if (!viewValue) { 2894 return null; 2895 } 2896 2897 if (angular.isDate(viewValue) && !isNaN(viewValue)) { 2898 return viewValue; 2899 } 2900 2901 if (angular.isString(viewValue)) { 2902 var date = parseDateString(viewValue); 2903 if (!isNaN(date)) { 2904 return dateParser.toTimezone(date, timezone); 2905 } 2906 } 2907 2908 return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined; 2909 } 2910 2911 function validator(modelValue, viewValue) { 2912 var value = modelValue || viewValue; 2913 2914 if (!$attrs.ngRequired && !value) { 2915 return true; 2916 } 2917 2918 if (angular.isNumber(value)) { 2919 value = new Date(value); 2920 } 2921 2922 if (!value) { 2923 return true; 2924 } 2925 2926 if (angular.isDate(value) && !isNaN(value)) { 2927 return true; 2928 } 2929 2930 if (angular.isString(value)) { 2931 return !isNaN(parseDateString(viewValue)); 2932 } 2933 2934 return false; 2935 } 2936 2937 function documentClickBind(event) { 2938 if (!$scope.isOpen && $scope.disabled) { 2939 return; 2940 } 2941 2942 var popup = $popup[0]; 2943 var dpContainsTarget = $element[0].contains(event.target); 2944 // The popup node may not be an element node 2945 // In some browsers (IE) only element nodes have the 'contains' function 2946 var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); 2947 if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { 2948 $scope.$apply(function() { 2949 $scope.isOpen = false; 2950 }); 2951 } 2952 } 2953 2954 function inputKeydownBind(evt) { 2955 if (evt.which === 27 && $scope.isOpen) { 2956 evt.preventDefault(); 2957 evt.stopPropagation(); 2958 $scope.$apply(function() { 2959 $scope.isOpen = false; 2960 }); 2961 $element[0].focus(); 2962 } else if (evt.which === 40 && !$scope.isOpen) { 2963 evt.preventDefault(); 2964 evt.stopPropagation(); 2965 $scope.$apply(function() { 2966 $scope.isOpen = true; 2967 }); 2968 } 2969 } 2970 2971 function positionPopup() { 2972 if ($scope.isOpen) { 2973 var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); 2974 var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; 2975 var position = $position.positionElements($element, dpElement, placement, appendToBody); 2976 dpElement.css({top: position.top + 'px', left: position.left + 'px'}); 2977 if (dpElement.hasClass('uib-position-measure')) { 2978 dpElement.removeClass('uib-position-measure'); 2979 } 2980 } 2981 } 2982 2983 $scope.$on('uib:datepicker.mode', function() { 2984 $timeout(positionPopup, 0, false); 2985 }); 2986}]) 2987 2988.directive('uibDatepickerPopup', function() { 2989 return { 2990 require: ['ngModel', 'uibDatepickerPopup'], 2991 controller: 'UibDatepickerPopupController', 2992 scope: { 2993 datepickerOptions: '=?', 2994 isOpen: '=?', 2995 currentText: '@', 2996 clearText: '@', 2997 closeText: '@' 2998 }, 2999 link: function(scope, element, attrs, ctrls) { 3000 var ngModel = ctrls[0], 3001 ctrl = ctrls[1]; 3002 3003 ctrl.init(ngModel); 3004 } 3005 }; 3006}) 3007 3008.directive('uibDatepickerPopupWrap', function() { 3009 return { 3010 replace: true, 3011 transclude: true, 3012 templateUrl: function(element, attrs) { 3013 return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html'; 3014 } 3015 }; 3016}); 3017 3018angular.module('ui.bootstrap.debounce', []) 3019/** 3020 * A helper, internal service that debounces a function 3021 */ 3022 .factory('$$debounce', ['$timeout', function($timeout) { 3023 return function(callback, debounceTime) { 3024 var timeoutPromise; 3025 3026 return function() { 3027 var self = this; 3028 var args = Array.prototype.slice.call(arguments); 3029 if (timeoutPromise) { 3030 $timeout.cancel(timeoutPromise); 3031 } 3032 3033 timeoutPromise = $timeout(function() { 3034 callback.apply(self, args); 3035 }, debounceTime); 3036 }; 3037 }; 3038 }]); 3039 3040angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position']) 3041 3042.constant('uibDropdownConfig', { 3043 appendToOpenClass: 'uib-dropdown-open', 3044 openClass: 'open' 3045}) 3046 3047.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) { 3048 var openScope = null; 3049 3050 this.open = function(dropdownScope, element) { 3051 if (!openScope) { 3052 $document.on('click', closeDropdown); 3053 element.on('keydown', keybindFilter); 3054 } 3055 3056 if (openScope && openScope !== dropdownScope) { 3057 openScope.isOpen = false; 3058 } 3059 3060 openScope = dropdownScope; 3061 }; 3062 3063 this.close = function(dropdownScope, element) { 3064 if (openScope === dropdownScope) { 3065 openScope = null; 3066 $document.off('click', closeDropdown); 3067 element.off('keydown', keybindFilter); 3068 } 3069 }; 3070 3071 var closeDropdown = function(evt) { 3072 // This method may still be called during the same mouse event that 3073 // unbound this event handler. So check openScope before proceeding. 3074 if (!openScope) { return; } 3075 3076 if (evt && openScope.getAutoClose() === 'disabled') { return; } 3077 3078 if (evt && evt.which === 3) { return; } 3079 3080 var toggleElement = openScope.getToggleElement(); 3081 if (evt && toggleElement && toggleElement[0].contains(evt.target)) { 3082 return; 3083 } 3084 3085 var dropdownElement = openScope.getDropdownElement(); 3086 if (evt && openScope.getAutoClose() === 'outsideClick' && 3087 dropdownElement && dropdownElement[0].contains(evt.target)) { 3088 return; 3089 } 3090 3091 openScope.isOpen = false; 3092 3093 if (!$rootScope.$$phase) { 3094 openScope.$apply(); 3095 } 3096 }; 3097 3098 var keybindFilter = function(evt) { 3099 if (evt.which === 27) { 3100 evt.stopPropagation(); 3101 openScope.focusToggleElement(); 3102 closeDropdown(); 3103 } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) { 3104 evt.preventDefault(); 3105 evt.stopPropagation(); 3106 openScope.focusDropdownEntry(evt.which); 3107 } 3108 }; 3109}]) 3110 3111.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) { 3112 var self = this, 3113 scope = $scope.$new(), // create a child scope so we are not polluting original one 3114 templateScope, 3115 appendToOpenClass = dropdownConfig.appendToOpenClass, 3116 openClass = dropdownConfig.openClass, 3117 getIsOpen, 3118 setIsOpen = angular.noop, 3119 toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop, 3120 appendToBody = false, 3121 appendTo = null, 3122 keynavEnabled = false, 3123 selectedOption = null, 3124 body = $document.find('body'); 3125 3126 $element.addClass('dropdown'); 3127 3128 this.init = function() { 3129 if ($attrs.isOpen) { 3130 getIsOpen = $parse($attrs.isOpen); 3131 setIsOpen = getIsOpen.assign; 3132 3133 $scope.$watch(getIsOpen, function(value) { 3134 scope.isOpen = !!value; 3135 }); 3136 } 3137 3138 if (angular.isDefined($attrs.dropdownAppendTo)) { 3139 var appendToEl = $parse($attrs.dropdownAppendTo)(scope); 3140 if (appendToEl) { 3141 appendTo = angular.element(appendToEl); 3142 } 3143 } 3144 3145 appendToBody = angular.isDefined($attrs.dropdownAppendToBody); 3146 keynavEnabled = angular.isDefined($attrs.keyboardNav); 3147 3148 if (appendToBody && !appendTo) { 3149 appendTo = body; 3150 } 3151 3152 if (appendTo && self.dropdownMenu) { 3153 appendTo.append(self.dropdownMenu); 3154 $element.on('$destroy', function handleDestroyEvent() { 3155 self.dropdownMenu.remove(); 3156 }); 3157 } 3158 }; 3159 3160 this.toggle = function(open) { 3161 scope.isOpen = arguments.length ? !!open : !scope.isOpen; 3162 if (angular.isFunction(setIsOpen)) { 3163 setIsOpen(scope, scope.isOpen); 3164 } 3165 3166 return scope.isOpen; 3167 }; 3168 3169 // Allow other directives to watch status 3170 this.isOpen = function() { 3171 return scope.isOpen; 3172 }; 3173 3174 scope.getToggleElement = function() { 3175 return self.toggleElement; 3176 }; 3177 3178 scope.getAutoClose = function() { 3179 return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled' 3180 }; 3181 3182 scope.getElement = function() { 3183 return $element; 3184 }; 3185 3186 scope.isKeynavEnabled = function() { 3187 return keynavEnabled; 3188 }; 3189 3190 scope.focusDropdownEntry = function(keyCode) { 3191 var elems = self.dropdownMenu ? //If append to body is used. 3192 angular.element(self.dropdownMenu).find('a') : 3193 $element.find('ul').eq(0).find('a'); 3194 3195 switch (keyCode) { 3196 case 40: { 3197 if (!angular.isNumber(self.selectedOption)) { 3198 self.selectedOption = 0; 3199 } else { 3200 self.selectedOption = self.selectedOption === elems.length - 1 ? 3201 self.selectedOption : 3202 self.selectedOption + 1; 3203 } 3204 break; 3205 } 3206 case 38: { 3207 if (!angular.isNumber(self.selectedOption)) { 3208 self.selectedOption = elems.length - 1; 3209 } else { 3210 self.selectedOption = self.selectedOption === 0 ? 3211 0 : self.selectedOption - 1; 3212 } 3213 break; 3214 } 3215 } 3216 elems[self.selectedOption].focus(); 3217 }; 3218 3219 scope.getDropdownElement = function() { 3220 return self.dropdownMenu; 3221 }; 3222 3223 scope.focusToggleElement = function() { 3224 if (self.toggleElement) { 3225 self.toggleElement[0].focus(); 3226 } 3227 }; 3228 3229 scope.$watch('isOpen', function(isOpen, wasOpen) { 3230 if (appendTo && self.dropdownMenu) { 3231 var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true), 3232 css, 3233 rightalign, 3234 scrollbarWidth; 3235 3236 css = { 3237 top: pos.top + 'px', 3238 display: isOpen ? 'block' : 'none' 3239 }; 3240 3241 rightalign = self.dropdownMenu.hasClass('dropdown-menu-right'); 3242 if (!rightalign) { 3243 css.left = pos.left + 'px'; 3244 css.right = 'auto'; 3245 } else { 3246 css.left = 'auto'; 3247 scrollbarWidth = $position.scrollbarWidth(true); 3248 css.right = window.innerWidth - scrollbarWidth - 3249 (pos.left + $element.prop('offsetWidth')) + 'px'; 3250 } 3251 3252 // Need to adjust our positioning to be relative to the appendTo container 3253 // if it's not the body element 3254 if (!appendToBody) { 3255 var appendOffset = $position.offset(appendTo); 3256 3257 css.top = pos.top - appendOffset.top + 'px'; 3258 3259 if (!rightalign) { 3260 css.left = pos.left - appendOffset.left + 'px'; 3261 } else { 3262 css.right = window.innerWidth - 3263 (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px'; 3264 } 3265 } 3266 3267 self.dropdownMenu.css(css); 3268 } 3269 3270 var openContainer = appendTo ? appendTo : $element; 3271 var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass); 3272 3273 if (hasOpenClass === !isOpen) { 3274 $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() { 3275 if (angular.isDefined(isOpen) && isOpen !== wasOpen) { 3276 toggleInvoker($scope, { open: !!isOpen }); 3277 } 3278 }); 3279 } 3280 3281 if (isOpen) { 3282 if (self.dropdownMenuTemplateUrl) { 3283 $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) { 3284 templateScope = scope.$new(); 3285 $compile(tplContent.trim())(templateScope, function(dropdownElement) { 3286 var newEl = dropdownElement; 3287 self.dropdownMenu.replaceWith(newEl); 3288 self.dropdownMenu = newEl; 3289 }); 3290 }); 3291 } 3292 3293 scope.focusToggleElement(); 3294 uibDropdownService.open(scope, $element); 3295 } else { 3296 if (self.dropdownMenuTemplateUrl) { 3297 if (templateScope) { 3298 templateScope.$destroy(); 3299 } 3300 var newEl = angular.element('<ul class="dropdown-menu"></ul>'); 3301 self.dropdownMenu.replaceWith(newEl); 3302 self.dropdownMenu = newEl; 3303 } 3304 3305 uibDropdownService.close(scope, $element); 3306 self.selectedOption = null; 3307 } 3308 3309 if (angular.isFunction(setIsOpen)) { 3310 setIsOpen($scope, isOpen); 3311 } 3312 }); 3313}]) 3314 3315.directive('uibDropdown', function() { 3316 return { 3317 controller: 'UibDropdownController', 3318 link: function(scope, element, attrs, dropdownCtrl) { 3319 dropdownCtrl.init(); 3320 } 3321 }; 3322}) 3323 3324.directive('uibDropdownMenu', function() { 3325 return { 3326 restrict: 'A', 3327 require: '?^uibDropdown', 3328 link: function(scope, element, attrs, dropdownCtrl) { 3329 if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) { 3330 return; 3331 } 3332 3333 element.addClass('dropdown-menu'); 3334 3335 var tplUrl = attrs.templateUrl; 3336 if (tplUrl) { 3337 dropdownCtrl.dropdownMenuTemplateUrl = tplUrl; 3338 } 3339 3340 if (!dropdownCtrl.dropdownMenu) { 3341 dropdownCtrl.dropdownMenu = element; 3342 } 3343 } 3344 }; 3345}) 3346 3347.directive('uibDropdownToggle', function() { 3348 return { 3349 require: '?^uibDropdown', 3350 link: function(scope, element, attrs, dropdownCtrl) { 3351 if (!dropdownCtrl) { 3352 return; 3353 } 3354 3355 element.addClass('dropdown-toggle'); 3356 3357 dropdownCtrl.toggleElement = element; 3358 3359 var toggleDropdown = function(event) { 3360 event.preventDefault(); 3361 3362 if (!element.hasClass('disabled') && !attrs.disabled) { 3363 scope.$apply(function() { 3364 dropdownCtrl.toggle(); 3365 }); 3366 } 3367 }; 3368 3369 element.bind('click', toggleDropdown); 3370 3371 // WAI-ARIA 3372 element.attr({ 'aria-haspopup': true, 'aria-expanded': false }); 3373 scope.$watch(dropdownCtrl.isOpen, function(isOpen) { 3374 element.attr('aria-expanded', !!isOpen); 3375 }); 3376 3377 scope.$on('$destroy', function() { 3378 element.unbind('click', toggleDropdown); 3379 }); 3380 } 3381 }; 3382}); 3383 3384angular.module('ui.bootstrap.stackedMap', []) 3385/** 3386 * A helper, internal data structure that acts as a map but also allows getting / removing 3387 * elements in the LIFO order 3388 */ 3389 .factory('$$stackedMap', function() { 3390 return { 3391 createNew: function() { 3392 var stack = []; 3393 3394 return { 3395 add: function(key, value) { 3396 stack.push({ 3397 key: key, 3398 value: value 3399 }); 3400 }, 3401 get: function(key) { 3402 for (var i = 0; i < stack.length; i++) { 3403 if (key === stack[i].key) { 3404 return stack[i]; 3405 } 3406 } 3407 }, 3408 keys: function() { 3409 var keys = []; 3410 for (var i = 0; i < stack.length; i++) { 3411 keys.push(stack[i].key); 3412 } 3413 return keys; 3414 }, 3415 top: function() { 3416 return stack[stack.length - 1]; 3417 }, 3418 remove: function(key) { 3419 var idx = -1; 3420 for (var i = 0; i < stack.length; i++) { 3421 if (key === stack[i].key) { 3422 idx = i; 3423 break; 3424 } 3425 } 3426 return stack.splice(idx, 1)[0]; 3427 }, 3428 removeTop: function() { 3429 return stack.splice(stack.length - 1, 1)[0]; 3430 }, 3431 length: function() { 3432 return stack.length; 3433 } 3434 }; 3435 } 3436 }; 3437 }); 3438angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position']) 3439/** 3440 * A helper, internal data structure that stores all references attached to key 3441 */ 3442 .factory('$$multiMap', function() { 3443 return { 3444 createNew: function() { 3445 var map = {}; 3446 3447 return { 3448 entries: function() { 3449 return Object.keys(map).map(function(key) { 3450 return { 3451 key: key, 3452 value: map[key] 3453 }; 3454 }); 3455 }, 3456 get: function(key) { 3457 return map[key]; 3458 }, 3459 hasKey: function(key) { 3460 return !!map[key]; 3461 }, 3462 keys: function() { 3463 return Object.keys(map); 3464 }, 3465 put: function(key, value) { 3466 if (!map[key]) { 3467 map[key] = []; 3468 } 3469 3470 map[key].push(value); 3471 }, 3472 remove: function(key, value) { 3473 var values = map[key]; 3474 3475 if (!values) { 3476 return; 3477 } 3478 3479 var idx = values.indexOf(value); 3480 3481 if (idx !== -1) { 3482 values.splice(idx, 1); 3483 } 3484 3485 if (!values.length) { 3486 delete map[key]; 3487 } 3488 } 3489 }; 3490 } 3491 }; 3492 }) 3493 3494/** 3495 * Pluggable resolve mechanism for the modal resolve resolution 3496 * Supports UI Router's $resolve service 3497 */ 3498 .provider('$uibResolve', function() { 3499 var resolve = this; 3500 this.resolver = null; 3501 3502 this.setResolver = function(resolver) { 3503 this.resolver = resolver; 3504 }; 3505 3506 this.$get = ['$injector', '$q', function($injector, $q) { 3507 var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null; 3508 return { 3509 resolve: function(invocables, locals, parent, self) { 3510 if (resolver) { 3511 return resolver.resolve(invocables, locals, parent, self); 3512 } 3513 3514 var promises = []; 3515 3516 angular.forEach(invocables, function(value) { 3517 if (angular.isFunction(value) || angular.isArray(value)) { 3518 promises.push($q.resolve($injector.invoke(value))); 3519 } else if (angular.isString(value)) { 3520 promises.push($q.resolve($injector.get(value))); 3521 } else { 3522 promises.push($q.resolve(value)); 3523 } 3524 }); 3525 3526 return $q.all(promises).then(function(resolves) { 3527 var resolveObj = {}; 3528 var resolveIter = 0; 3529 angular.forEach(invocables, function(value, key) { 3530 resolveObj[key] = resolves[resolveIter++]; 3531 }); 3532 3533 return resolveObj; 3534 }); 3535 } 3536 }; 3537 }]; 3538 }) 3539 3540/** 3541 * A helper directive for the $modal service. It creates a backdrop element. 3542 */ 3543 .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack', 3544 function($animate, $injector, $modalStack) { 3545 return { 3546 replace: true, 3547 templateUrl: 'uib/template/modal/backdrop.html', 3548 compile: function(tElement, tAttrs) { 3549 tElement.addClass(tAttrs.backdropClass); 3550 return linkFn; 3551 } 3552 }; 3553 3554 function linkFn(scope, element, attrs) { 3555 if (attrs.modalInClass) { 3556 $animate.addClass(element, attrs.modalInClass); 3557 3558 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { 3559 var done = setIsAsync(); 3560 if (scope.modalOptions.animation) { 3561 $animate.removeClass(element, attrs.modalInClass).then(done); 3562 } else { 3563 done(); 3564 } 3565 }); 3566 } 3567 } 3568 }]) 3569 3570 .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document', 3571 function($modalStack, $q, $animateCss, $document) { 3572 return { 3573 scope: { 3574 index: '@' 3575 }, 3576 replace: true, 3577 transclude: true, 3578 templateUrl: function(tElement, tAttrs) { 3579 return tAttrs.templateUrl || 'uib/template/modal/window.html'; 3580 }, 3581 link: function(scope, element, attrs) { 3582 element.addClass(attrs.windowClass || ''); 3583 element.addClass(attrs.windowTopClass || ''); 3584 scope.size = attrs.size; 3585 3586 scope.close = function(evt) { 3587 var modal = $modalStack.getTop(); 3588 if (modal && modal.value.backdrop && 3589 modal.value.backdrop !== 'static' && 3590 evt.target === evt.currentTarget) { 3591 evt.preventDefault(); 3592 evt.stopPropagation(); 3593 $modalStack.dismiss(modal.key, 'backdrop click'); 3594 } 3595 }; 3596 3597 // moved from template to fix issue #2280 3598 element.on('click', scope.close); 3599 3600 // This property is only added to the scope for the purpose of detecting when this directive is rendered. 3601 // We can detect that by using this property in the template associated with this directive and then use 3602 // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}. 3603 scope.$isRendered = true; 3604 3605 // Deferred object that will be resolved when this modal is render. 3606 var modalRenderDeferObj = $q.defer(); 3607 // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready. 3608 // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template. 3609 attrs.$observe('modalRender', function(value) { 3610 if (value === 'true') { 3611 modalRenderDeferObj.resolve(); 3612 } 3613 }); 3614 3615 modalRenderDeferObj.promise.then(function() { 3616 var animationPromise = null; 3617 3618 if (attrs.modalInClass) { 3619 animationPromise = $animateCss(element, { 3620 addClass: attrs.modalInClass 3621 }).start(); 3622 3623 scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) { 3624 var done = setIsAsync(); 3625 $animateCss(element, { 3626 removeClass: attrs.modalInClass 3627 }).start().then(done); 3628 }); 3629 } 3630 3631 3632 $q.when(animationPromise).then(function() { 3633 // Notify {@link $modalStack} that modal is rendered. 3634 var modal = $modalStack.getTop(); 3635 if (modal) { 3636 $modalStack.modalRendered(modal.key); 3637 } 3638 3639 /** 3640 * If something within the freshly-opened modal already has focus (perhaps via a 3641 * directive that causes focus). then no need to try and focus anything. 3642 */ 3643 if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) { 3644 var inputWithAutofocus = element[0].querySelector('[autofocus]'); 3645 /** 3646 * Auto-focusing of a freshly-opened modal element causes any child elements 3647 * with the autofocus attribute to lose focus. This is an issue on touch 3648 * based devices which will show and then hide the onscreen keyboard. 3649 * Attempts to refocus the autofocus element via JavaScript will not reopen 3650 * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus 3651 * the modal element if the modal does not contain an autofocus element. 3652 */ 3653 if (inputWithAutofocus) { 3654 inputWithAutofocus.focus(); 3655 } else { 3656 element[0].focus(); 3657 } 3658 } 3659 }); 3660 }); 3661 } 3662 }; 3663 }]) 3664 3665 .directive('uibModalAnimationClass', function() { 3666 return { 3667 compile: function(tElement, tAttrs) { 3668 if (tAttrs.modalAnimation) { 3669 tElement.addClass(tAttrs.uibModalAnimationClass); 3670 } 3671 } 3672 }; 3673 }) 3674 3675 .directive('uibModalTransclude', function() { 3676 return { 3677 link: function(scope, element, attrs, controller, transclude) { 3678 transclude(scope.$parent, function(clone) { 3679 element.empty(); 3680 element.append(clone); 3681 }); 3682 } 3683 }; 3684 }) 3685 3686 .factory('$uibModalStack', ['$animate', '$animateCss', '$document', 3687 '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition', 3688 function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) { 3689 var OPENED_MODAL_CLASS = 'modal-open'; 3690 3691 var backdropDomEl, backdropScope; 3692 var openedWindows = $$stackedMap.createNew(); 3693 var openedClasses = $$multiMap.createNew(); 3694 var $modalStack = { 3695 NOW_CLOSING_EVENT: 'modal.stack.now-closing' 3696 }; 3697 var topModalIndex = 0; 3698 var previousTopOpenedModal = null; 3699 3700 //Modal focus behavior 3701 var tabableSelector = 'a[href], area[href], input:not([disabled]), ' + 3702 'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' + 3703 'iframe, object, embed, *[tabindex], *[contenteditable=true]'; 3704 var scrollbarPadding; 3705 3706 function isVisible(element) { 3707 return !!(element.offsetWidth || 3708 element.offsetHeight || 3709 element.getClientRects().length); 3710 } 3711 3712 function backdropIndex() { 3713 var topBackdropIndex = -1; 3714 var opened = openedWindows.keys(); 3715 for (var i = 0; i < opened.length; i++) { 3716 if (openedWindows.get(opened[i]).value.backdrop) { 3717 topBackdropIndex = i; 3718 } 3719 } 3720 3721 // If any backdrop exist, ensure that it's index is always 3722 // right below the top modal 3723 if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) { 3724 topBackdropIndex = topModalIndex; 3725 } 3726 return topBackdropIndex; 3727 } 3728 3729 $rootScope.$watch(backdropIndex, function(newBackdropIndex) { 3730 if (backdropScope) { 3731 backdropScope.index = newBackdropIndex; 3732 } 3733 }); 3734 3735 function removeModalWindow(modalInstance, elementToReceiveFocus) { 3736 var modalWindow = openedWindows.get(modalInstance).value; 3737 var appendToElement = modalWindow.appendTo; 3738 3739 //clean up the stack 3740 openedWindows.remove(modalInstance); 3741 previousTopOpenedModal = openedWindows.top(); 3742 if (previousTopOpenedModal) { 3743 topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10); 3744 } 3745 3746 removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() { 3747 var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS; 3748 openedClasses.remove(modalBodyClass, modalInstance); 3749 var areAnyOpen = openedClasses.hasKey(modalBodyClass); 3750 appendToElement.toggleClass(modalBodyClass, areAnyOpen); 3751 if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { 3752 if (scrollbarPadding.originalRight) { 3753 appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'}); 3754 } else { 3755 appendToElement.css({paddingRight: ''}); 3756 } 3757 scrollbarPadding = null; 3758 } 3759 toggleTopWindowClass(true); 3760 }, modalWindow.closedDeferred); 3761 checkRemoveBackdrop(); 3762 3763 //move focus to specified element if available, or else to body 3764 if (elementToReceiveFocus && elementToReceiveFocus.focus) { 3765 elementToReceiveFocus.focus(); 3766 } else if (appendToElement.focus) { 3767 appendToElement.focus(); 3768 } 3769 } 3770 3771 // Add or remove "windowTopClass" from the top window in the stack 3772 function toggleTopWindowClass(toggleSwitch) { 3773 var modalWindow; 3774 3775 if (openedWindows.length() > 0) { 3776 modalWindow = openedWindows.top().value; 3777 modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch); 3778 } 3779 } 3780 3781 function checkRemoveBackdrop() { 3782 //remove backdrop if no longer needed 3783 if (backdropDomEl && backdropIndex() === -1) { 3784 var backdropScopeRef = backdropScope; 3785 removeAfterAnimate(backdropDomEl, backdropScope, function() { 3786 backdropScopeRef = null; 3787 }); 3788 backdropDomEl = undefined; 3789 backdropScope = undefined; 3790 } 3791 } 3792 3793 function removeAfterAnimate(domEl, scope, done, closedDeferred) { 3794 var asyncDeferred; 3795 var asyncPromise = null; 3796 var setIsAsync = function() { 3797 if (!asyncDeferred) { 3798 asyncDeferred = $q.defer(); 3799 asyncPromise = asyncDeferred.promise; 3800 } 3801 3802 return function asyncDone() { 3803 asyncDeferred.resolve(); 3804 }; 3805 }; 3806 scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync); 3807 3808 // Note that it's intentional that asyncPromise might be null. 3809 // That's when setIsAsync has not been called during the 3810 // NOW_CLOSING_EVENT broadcast. 3811 return $q.when(asyncPromise).then(afterAnimating); 3812 3813 function afterAnimating() { 3814 if (afterAnimating.done) { 3815 return; 3816 } 3817 afterAnimating.done = true; 3818 3819 $animate.leave(domEl).then(function() { 3820 domEl.remove(); 3821 if (closedDeferred) { 3822 closedDeferred.resolve(); 3823 } 3824 }); 3825 3826 scope.$destroy(); 3827 if (done) { 3828 done(); 3829 } 3830 } 3831 } 3832 3833 $document.on('keydown', keydownListener); 3834 3835 $rootScope.$on('$destroy', function() { 3836 $document.off('keydown', keydownListener); 3837 }); 3838 3839 function keydownListener(evt) { 3840 if (evt.isDefaultPrevented()) { 3841 return evt; 3842 } 3843 3844 var modal = openedWindows.top(); 3845 if (modal) { 3846 switch (evt.which) { 3847 case 27: { 3848 if (modal.value.keyboard) { 3849 evt.preventDefault(); 3850 $rootScope.$apply(function() { 3851 $modalStack.dismiss(modal.key, 'escape key press'); 3852 }); 3853 } 3854 break; 3855 } 3856 case 9: { 3857 var list = $modalStack.loadFocusElementList(modal); 3858 var focusChanged = false; 3859 if (evt.shiftKey) { 3860 if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) { 3861 focusChanged = $modalStack.focusLastFocusableElement(list); 3862 } 3863 } else { 3864 if ($modalStack.isFocusInLastItem(evt, list)) { 3865 focusChanged = $modalStack.focusFirstFocusableElement(list); 3866 } 3867 } 3868 3869 if (focusChanged) { 3870 evt.preventDefault(); 3871 evt.stopPropagation(); 3872 } 3873 3874 break; 3875 } 3876 } 3877 } 3878 } 3879 3880 $modalStack.open = function(modalInstance, modal) { 3881 var modalOpener = $document[0].activeElement, 3882 modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS; 3883 3884 toggleTopWindowClass(false); 3885 3886 // Store the current top first, to determine what index we ought to use 3887 // for the current top modal 3888 previousTopOpenedModal = openedWindows.top(); 3889 3890 openedWindows.add(modalInstance, { 3891 deferred: modal.deferred, 3892 renderDeferred: modal.renderDeferred, 3893 closedDeferred: modal.closedDeferred, 3894 modalScope: modal.scope, 3895 backdrop: modal.backdrop, 3896 keyboard: modal.keyboard, 3897 openedClass: modal.openedClass, 3898 windowTopClass: modal.windowTopClass, 3899 animation: modal.animation, 3900 appendTo: modal.appendTo 3901 }); 3902 3903 openedClasses.put(modalBodyClass, modalInstance); 3904 3905 var appendToElement = modal.appendTo, 3906 currBackdropIndex = backdropIndex(); 3907 3908 if (!appendToElement.length) { 3909 throw new Error('appendTo element not found. Make sure that the element passed is in DOM.'); 3910 } 3911 3912 if (currBackdropIndex >= 0 && !backdropDomEl) { 3913 backdropScope = $rootScope.$new(true); 3914 backdropScope.modalOptions = modal; 3915 backdropScope.index = currBackdropIndex; 3916 backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>'); 3917 backdropDomEl.attr('backdrop-class', modal.backdropClass); 3918 if (modal.animation) { 3919 backdropDomEl.attr('modal-animation', 'true'); 3920 } 3921 $compile(backdropDomEl)(backdropScope); 3922 $animate.enter(backdropDomEl, appendToElement); 3923 scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement); 3924 if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) { 3925 appendToElement.css({paddingRight: scrollbarPadding.right + 'px'}); 3926 } 3927 } 3928 3929 // Set the top modal index based on the index of the previous top modal 3930 topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0; 3931 var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>'); 3932 angularDomEl.attr({ 3933 'template-url': modal.windowTemplateUrl, 3934 'window-class': modal.windowClass, 3935 'window-top-class': modal.windowTopClass, 3936 'size': modal.size, 3937 'index': topModalIndex, 3938 'animate': 'animate' 3939 }).html(modal.content); 3940 if (modal.animation) { 3941 angularDomEl.attr('modal-animation', 'true'); 3942 } 3943 3944 appendToElement.addClass(modalBodyClass); 3945 $animate.enter($compile(angularDomEl)(modal.scope), appendToElement); 3946 3947 openedWindows.top().value.modalDomEl = angularDomEl; 3948 openedWindows.top().value.modalOpener = modalOpener; 3949 }; 3950 3951 function broadcastClosing(modalWindow, resultOrReason, closing) { 3952 return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented; 3953 } 3954 3955 $modalStack.close = function(modalInstance, result) { 3956 var modalWindow = openedWindows.get(modalInstance); 3957 if (modalWindow && broadcastClosing(modalWindow, result, true)) { 3958 modalWindow.value.modalScope.$$uibDestructionScheduled = true; 3959 modalWindow.value.deferred.resolve(result); 3960 removeModalWindow(modalInstance, modalWindow.value.modalOpener); 3961 return true; 3962 } 3963 return !modalWindow; 3964 }; 3965 3966 $modalStack.dismiss = function(modalInstance, reason) { 3967 var modalWindow = openedWindows.get(modalInstance); 3968 if (modalWindow && broadcastClosing(modalWindow, reason, false)) { 3969 modalWindow.value.modalScope.$$uibDestructionScheduled = true; 3970 modalWindow.value.deferred.reject(reason); 3971 removeModalWindow(modalInstance, modalWindow.value.modalOpener); 3972 return true; 3973 } 3974 return !modalWindow; 3975 }; 3976 3977 $modalStack.dismissAll = function(reason) { 3978 var topModal = this.getTop(); 3979 while (topModal && this.dismiss(topModal.key, reason)) { 3980 topModal = this.getTop(); 3981 } 3982 }; 3983 3984 $modalStack.getTop = function() { 3985 return openedWindows.top(); 3986 }; 3987 3988 $modalStack.modalRendered = function(modalInstance) { 3989 var modalWindow = openedWindows.get(modalInstance); 3990 if (modalWindow) { 3991 modalWindow.value.renderDeferred.resolve(); 3992 } 3993 }; 3994 3995 $modalStack.focusFirstFocusableElement = function(list) { 3996 if (list.length > 0) { 3997 list[0].focus(); 3998 return true; 3999 } 4000 return false; 4001 }; 4002 4003 $modalStack.focusLastFocusableElement = function(list) { 4004 if (list.length > 0) { 4005 list[list.length - 1].focus(); 4006 return true; 4007 } 4008 return false; 4009 }; 4010 4011 $modalStack.isModalFocused = function(evt, modalWindow) { 4012 if (evt && modalWindow) { 4013 var modalDomEl = modalWindow.value.modalDomEl; 4014 if (modalDomEl && modalDomEl.length) { 4015 return (evt.target || evt.srcElement) === modalDomEl[0]; 4016 } 4017 } 4018 return false; 4019 }; 4020 4021 $modalStack.isFocusInFirstItem = function(evt, list) { 4022 if (list.length > 0) { 4023 return (evt.target || evt.srcElement) === list[0]; 4024 } 4025 return false; 4026 }; 4027 4028 $modalStack.isFocusInLastItem = function(evt, list) { 4029 if (list.length > 0) { 4030 return (evt.target || evt.srcElement) === list[list.length - 1]; 4031 } 4032 return false; 4033 }; 4034 4035 $modalStack.loadFocusElementList = function(modalWindow) { 4036 if (modalWindow) { 4037 var modalDomE1 = modalWindow.value.modalDomEl; 4038 if (modalDomE1 && modalDomE1.length) { 4039 var elements = modalDomE1[0].querySelectorAll(tabableSelector); 4040 return elements ? 4041 Array.prototype.filter.call(elements, function(element) { 4042 return isVisible(element); 4043 }) : elements; 4044 } 4045 } 4046 }; 4047 4048 return $modalStack; 4049 }]) 4050 4051 .provider('$uibModal', function() { 4052 var $modalProvider = { 4053 options: { 4054 animation: true, 4055 backdrop: true, //can also be false or 'static' 4056 keyboard: true 4057 }, 4058 $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack', 4059 function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) { 4060 var $modal = {}; 4061 4062 function getTemplatePromise(options) { 4063 return options.template ? $q.when(options.template) : 4064 $templateRequest(angular.isFunction(options.templateUrl) ? 4065 options.templateUrl() : options.templateUrl); 4066 } 4067 4068 var promiseChain = null; 4069 $modal.getPromiseChain = function() { 4070 return promiseChain; 4071 }; 4072 4073 $modal.open = function(modalOptions) { 4074 var modalResultDeferred = $q.defer(); 4075 var modalOpenedDeferred = $q.defer(); 4076 var modalClosedDeferred = $q.defer(); 4077 var modalRenderDeferred = $q.defer(); 4078 4079 //prepare an instance of a modal to be injected into controllers and returned to a caller 4080 var modalInstance = { 4081 result: modalResultDeferred.promise, 4082 opened: modalOpenedDeferred.promise, 4083 closed: modalClosedDeferred.promise, 4084 rendered: modalRenderDeferred.promise, 4085 close: function (result) { 4086 return $modalStack.close(modalInstance, result); 4087 }, 4088 dismiss: function (reason) { 4089 return $modalStack.dismiss(modalInstance, reason); 4090 } 4091 }; 4092 4093 //merge and clean up options 4094 modalOptions = angular.extend({}, $modalProvider.options, modalOptions); 4095 modalOptions.resolve = modalOptions.resolve || {}; 4096 modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0); 4097 4098 //verify options 4099 if (!modalOptions.template && !modalOptions.templateUrl) { 4100 throw new Error('One of template or templateUrl options is required.'); 4101 } 4102 4103 var templateAndResolvePromise = 4104 $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]); 4105 4106 function resolveWithTemplate() { 4107 return templateAndResolvePromise; 4108 } 4109 4110 // Wait for the resolution of the existing promise chain. 4111 // Then switch to our own combined promise dependency (regardless of how the previous modal fared). 4112 // Then add to $modalStack and resolve opened. 4113 // Finally clean up the chain variable if no subsequent modal has overwritten it. 4114 var samePromise; 4115 samePromise = promiseChain = $q.all([promiseChain]) 4116 .then(resolveWithTemplate, resolveWithTemplate) 4117 .then(function resolveSuccess(tplAndVars) { 4118 var providedScope = modalOptions.scope || $rootScope; 4119 4120 var modalScope = providedScope.$new(); 4121 modalScope.$close = modalInstance.close; 4122 modalScope.$dismiss = modalInstance.dismiss; 4123 4124 modalScope.$on('$destroy', function() { 4125 if (!modalScope.$$uibDestructionScheduled) { 4126 modalScope.$dismiss('$uibUnscheduledDestruction'); 4127 } 4128 }); 4129 4130 var ctrlInstance, ctrlInstantiate, ctrlLocals = {}; 4131 4132 //controllers 4133 if (modalOptions.controller) { 4134 ctrlLocals.$scope = modalScope; 4135 ctrlLocals.$scope.$resolve = {}; 4136 ctrlLocals.$uibModalInstance = modalInstance; 4137 angular.forEach(tplAndVars[1], function(value, key) { 4138 ctrlLocals[key] = value; 4139 ctrlLocals.$scope.$resolve[key] = value; 4140 }); 4141 4142 // the third param will make the controller instantiate later,private api 4143 // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126 4144 ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true, modalOptions.controllerAs); 4145 if (modalOptions.controllerAs && modalOptions.bindToController) { 4146 ctrlInstance = ctrlInstantiate.instance; 4147 ctrlInstance.$close = modalScope.$close; 4148 ctrlInstance.$dismiss = modalScope.$dismiss; 4149 angular.extend(ctrlInstance, { 4150 $resolve: ctrlLocals.$scope.$resolve 4151 }, providedScope); 4152 } 4153 4154 ctrlInstance = ctrlInstantiate(); 4155 4156 if (angular.isFunction(ctrlInstance.$onInit)) { 4157 ctrlInstance.$onInit(); 4158 } 4159 } 4160 4161 $modalStack.open(modalInstance, { 4162 scope: modalScope, 4163 deferred: modalResultDeferred, 4164 renderDeferred: modalRenderDeferred, 4165 closedDeferred: modalClosedDeferred, 4166 content: tplAndVars[0], 4167 animation: modalOptions.animation, 4168 backdrop: modalOptions.backdrop, 4169 keyboard: modalOptions.keyboard, 4170 backdropClass: modalOptions.backdropClass, 4171 windowTopClass: modalOptions.windowTopClass, 4172 windowClass: modalOptions.windowClass, 4173 windowTemplateUrl: modalOptions.windowTemplateUrl, 4174 size: modalOptions.size, 4175 openedClass: modalOptions.openedClass, 4176 appendTo: modalOptions.appendTo 4177 }); 4178 modalOpenedDeferred.resolve(true); 4179 4180 }, function resolveError(reason) { 4181 modalOpenedDeferred.reject(reason); 4182 modalResultDeferred.reject(reason); 4183 })['finally'](function() { 4184 if (promiseChain === samePromise) { 4185 promiseChain = null; 4186 } 4187 }); 4188 4189 return modalInstance; 4190 }; 4191 4192 return $modal; 4193 } 4194 ] 4195 }; 4196 4197 return $modalProvider; 4198 }); 4199 4200angular.module('ui.bootstrap.paging', []) 4201/** 4202 * Helper internal service for generating common controller code between the 4203 * pager and pagination components 4204 */ 4205.factory('uibPaging', ['$parse', function($parse) { 4206 return { 4207 create: function(ctrl, $scope, $attrs) { 4208 ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop; 4209 ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl 4210 ctrl._watchers = []; 4211 4212 ctrl.init = function(ngModelCtrl, config) { 4213 ctrl.ngModelCtrl = ngModelCtrl; 4214 ctrl.config = config; 4215 4216 ngModelCtrl.$render = function() { 4217 ctrl.render(); 4218 }; 4219 4220 if ($attrs.itemsPerPage) { 4221 ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) { 4222 ctrl.itemsPerPage = parseInt(value, 10); 4223 $scope.totalPages = ctrl.calculateTotalPages(); 4224 ctrl.updatePage(); 4225 })); 4226 } else { 4227 ctrl.itemsPerPage = config.itemsPerPage; 4228 } 4229 4230 $scope.$watch('totalItems', function(newTotal, oldTotal) { 4231 if (angular.isDefined(newTotal) || newTotal !== oldTotal) { 4232 $scope.totalPages = ctrl.calculateTotalPages(); 4233 ctrl.updatePage(); 4234 } 4235 }); 4236 }; 4237 4238 ctrl.calculateTotalPages = function() { 4239 var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage); 4240 return Math.max(totalPages || 0, 1); 4241 }; 4242 4243 ctrl.render = function() { 4244 $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1; 4245 }; 4246 4247 $scope.selectPage = function(page, evt) { 4248 if (evt) { 4249 evt.preventDefault(); 4250 } 4251 4252 var clickAllowed = !$scope.ngDisabled || !evt; 4253 if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) { 4254 if (evt && evt.target) { 4255 evt.target.blur(); 4256 } 4257 ctrl.ngModelCtrl.$setViewValue(page); 4258 ctrl.ngModelCtrl.$render(); 4259 } 4260 }; 4261 4262 $scope.getText = function(key) { 4263 return $scope[key + 'Text'] || ctrl.config[key + 'Text']; 4264 }; 4265 4266 $scope.noPrevious = function() { 4267 return $scope.page === 1; 4268 }; 4269 4270 $scope.noNext = function() { 4271 return $scope.page === $scope.totalPages; 4272 }; 4273 4274 ctrl.updatePage = function() { 4275 ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable 4276 4277 if ($scope.page > $scope.totalPages) { 4278 $scope.selectPage($scope.totalPages); 4279 } else { 4280 ctrl.ngModelCtrl.$render(); 4281 } 4282 }; 4283 4284 $scope.$on('$destroy', function() { 4285 while (ctrl._watchers.length) { 4286 ctrl._watchers.shift()(); 4287 } 4288 }); 4289 } 4290 }; 4291}]); 4292 4293angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging']) 4294 4295.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) { 4296 $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align; 4297 4298 uibPaging.create(this, $scope, $attrs); 4299}]) 4300 4301.constant('uibPagerConfig', { 4302 itemsPerPage: 10, 4303 previousText: '« Previous', 4304 nextText: 'Next »', 4305 align: true 4306}) 4307 4308.directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) { 4309 return { 4310 scope: { 4311 totalItems: '=', 4312 previousText: '@', 4313 nextText: '@', 4314 ngDisabled: '=' 4315 }, 4316 require: ['uibPager', '?ngModel'], 4317 controller: 'UibPagerController', 4318 controllerAs: 'pager', 4319 templateUrl: function(element, attrs) { 4320 return attrs.templateUrl || 'uib/template/pager/pager.html'; 4321 }, 4322 replace: true, 4323 link: function(scope, element, attrs, ctrls) { 4324 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 4325 4326 if (!ngModelCtrl) { 4327 return; // do nothing if no ng-model 4328 } 4329 4330 paginationCtrl.init(ngModelCtrl, uibPagerConfig); 4331 } 4332 }; 4333}]); 4334 4335angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging']) 4336.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) { 4337 var ctrl = this; 4338 // Setup configuration parameters 4339 var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize, 4340 rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate, 4341 forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses, 4342 boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers, 4343 pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity; 4344 $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks; 4345 $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks; 4346 4347 uibPaging.create(this, $scope, $attrs); 4348 4349 if ($attrs.maxSize) { 4350 ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) { 4351 maxSize = parseInt(value, 10); 4352 ctrl.render(); 4353 })); 4354 } 4355 4356 // Create page object used in template 4357 function makePage(number, text, isActive) { 4358 return { 4359 number: number, 4360 text: text, 4361 active: isActive 4362 }; 4363 } 4364 4365 function getPages(currentPage, totalPages) { 4366 var pages = []; 4367 4368 // Default page limits 4369 var startPage = 1, endPage = totalPages; 4370 var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages; 4371 4372 // recompute if maxSize 4373 if (isMaxSized) { 4374 if (rotate) { 4375 // Current page is displayed in the middle of the visible ones 4376 startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1); 4377 endPage = startPage + maxSize - 1; 4378 4379 // Adjust if limit is exceeded 4380 if (endPage > totalPages) { 4381 endPage = totalPages; 4382 startPage = endPage - maxSize + 1; 4383 } 4384 } else { 4385 // Visible pages are paginated with maxSize 4386 startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1; 4387 4388 // Adjust last page if limit is exceeded 4389 endPage = Math.min(startPage + maxSize - 1, totalPages); 4390 } 4391 } 4392 4393 // Add page number links 4394 for (var number = startPage; number <= endPage; number++) { 4395 var page = makePage(number, pageLabel(number), number === currentPage); 4396 pages.push(page); 4397 } 4398 4399 // Add links to move between page sets 4400 if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) { 4401 if (startPage > 1) { 4402 if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning 4403 var previousPageSet = makePage(startPage - 1, '...', false); 4404 pages.unshift(previousPageSet); 4405 } 4406 if (boundaryLinkNumbers) { 4407 if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential 4408 var secondPageLink = makePage(2, '2', false); 4409 pages.unshift(secondPageLink); 4410 } 4411 //add the first page 4412 var firstPageLink = makePage(1, '1', false); 4413 pages.unshift(firstPageLink); 4414 } 4415 } 4416 4417 if (endPage < totalPages) { 4418 if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end 4419 var nextPageSet = makePage(endPage + 1, '...', false); 4420 pages.push(nextPageSet); 4421 } 4422 if (boundaryLinkNumbers) { 4423 if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential 4424 var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false); 4425 pages.push(secondToLastPageLink); 4426 } 4427 //add the last page 4428 var lastPageLink = makePage(totalPages, totalPages, false); 4429 pages.push(lastPageLink); 4430 } 4431 } 4432 } 4433 return pages; 4434 } 4435 4436 var originalRender = this.render; 4437 this.render = function() { 4438 originalRender(); 4439 if ($scope.page > 0 && $scope.page <= $scope.totalPages) { 4440 $scope.pages = getPages($scope.page, $scope.totalPages); 4441 } 4442 }; 4443}]) 4444 4445.constant('uibPaginationConfig', { 4446 itemsPerPage: 10, 4447 boundaryLinks: false, 4448 boundaryLinkNumbers: false, 4449 directionLinks: true, 4450 firstText: 'First', 4451 previousText: 'Previous', 4452 nextText: 'Next', 4453 lastText: 'Last', 4454 rotate: true, 4455 forceEllipses: false 4456}) 4457 4458.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) { 4459 return { 4460 scope: { 4461 totalItems: '=', 4462 firstText: '@', 4463 previousText: '@', 4464 nextText: '@', 4465 lastText: '@', 4466 ngDisabled:'=' 4467 }, 4468 require: ['uibPagination', '?ngModel'], 4469 controller: 'UibPaginationController', 4470 controllerAs: 'pagination', 4471 templateUrl: function(element, attrs) { 4472 return attrs.templateUrl || 'uib/template/pagination/pagination.html'; 4473 }, 4474 replace: true, 4475 link: function(scope, element, attrs, ctrls) { 4476 var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 4477 4478 if (!ngModelCtrl) { 4479 return; // do nothing if no ng-model 4480 } 4481 4482 paginationCtrl.init(ngModelCtrl, uibPaginationConfig); 4483 } 4484 }; 4485}]); 4486 4487/** 4488 * The following features are still outstanding: animation as a 4489 * function, placement as a function, inside, support for more triggers than 4490 * just mouse enter/leave, html tooltips, and selector delegation. 4491 */ 4492angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap']) 4493 4494/** 4495 * The $tooltip service creates tooltip- and popover-like directives as well as 4496 * houses global options for them. 4497 */ 4498.provider('$uibTooltip', function() { 4499 // The default options tooltip and popover. 4500 var defaultOptions = { 4501 placement: 'top', 4502 placementClassPrefix: '', 4503 animation: true, 4504 popupDelay: 0, 4505 popupCloseDelay: 0, 4506 useContentExp: false 4507 }; 4508 4509 // Default hide triggers for each show trigger 4510 var triggerMap = { 4511 'mouseenter': 'mouseleave', 4512 'click': 'click', 4513 'outsideClick': 'outsideClick', 4514 'focus': 'blur', 4515 'none': '' 4516 }; 4517 4518 // The options specified to the provider globally. 4519 var globalOptions = {}; 4520 4521 /** 4522 * `options({})` allows global configuration of all tooltips in the 4523 * application. 4524 * 4525 * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) { 4526 * // place tooltips left instead of top by default 4527 * $tooltipProvider.options( { placement: 'left' } ); 4528 * }); 4529 */ 4530 this.options = function(value) { 4531 angular.extend(globalOptions, value); 4532 }; 4533 4534 /** 4535 * This allows you to extend the set of trigger mappings available. E.g.: 4536 * 4537 * $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } ); 4538 */ 4539 this.setTriggers = function setTriggers(triggers) { 4540 angular.extend(triggerMap, triggers); 4541 }; 4542 4543 /** 4544 * This is a helper function for translating camel-case to snake_case. 4545 */ 4546 function snake_case(name) { 4547 var regexp = /[A-Z]/g; 4548 var separator = '-'; 4549 return name.replace(regexp, function(letter, pos) { 4550 return (pos ? separator : '') + letter.toLowerCase(); 4551 }); 4552 } 4553 4554 /** 4555 * Returns the actual instance of the $tooltip service. 4556 * TODO support multiple triggers 4557 */ 4558 this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) { 4559 var openedTooltips = $$stackedMap.createNew(); 4560 $document.on('keypress', keypressListener); 4561 4562 $rootScope.$on('$destroy', function() { 4563 $document.off('keypress', keypressListener); 4564 }); 4565 4566 function keypressListener(e) { 4567 if (e.which === 27) { 4568 var last = openedTooltips.top(); 4569 if (last) { 4570 last.value.close(); 4571 openedTooltips.removeTop(); 4572 last = null; 4573 } 4574 } 4575 } 4576 4577 return function $tooltip(ttType, prefix, defaultTriggerShow, options) { 4578 options = angular.extend({}, defaultOptions, globalOptions, options); 4579 4580 /** 4581 * Returns an object of show and hide triggers. 4582 * 4583 * If a trigger is supplied, 4584 * it is used to show the tooltip; otherwise, it will use the `trigger` 4585 * option passed to the `$tooltipProvider.options` method; else it will 4586 * default to the trigger supplied to this directive factory. 4587 * 4588 * The hide trigger is based on the show trigger. If the `trigger` option 4589 * was passed to the `$tooltipProvider.options` method, it will use the 4590 * mapped trigger from `triggerMap` or the passed trigger if the map is 4591 * undefined; otherwise, it uses the `triggerMap` value of the show 4592 * trigger; else it will just use the show trigger. 4593 */ 4594 function getTriggers(trigger) { 4595 var show = (trigger || options.trigger || defaultTriggerShow).split(' '); 4596 var hide = show.map(function(trigger) { 4597 return triggerMap[trigger] || trigger; 4598 }); 4599 return { 4600 show: show, 4601 hide: hide 4602 }; 4603 } 4604 4605 var directiveName = snake_case(ttType); 4606 4607 var startSym = $interpolate.startSymbol(); 4608 var endSym = $interpolate.endSymbol(); 4609 var template = 4610 '<div '+ directiveName + '-popup ' + 4611 'uib-title="' + startSym + 'title' + endSym + '" ' + 4612 (options.useContentExp ? 4613 'content-exp="contentExp()" ' : 4614 'content="' + startSym + 'content' + endSym + '" ') + 4615 'placement="' + startSym + 'placement' + endSym + '" ' + 4616 'popup-class="' + startSym + 'popupClass' + endSym + '" ' + 4617 'animation="animation" ' + 4618 'is-open="isOpen" ' + 4619 'origin-scope="origScope" ' + 4620 'class="uib-position-measure"' + 4621 '>' + 4622 '</div>'; 4623 4624 return { 4625 compile: function(tElem, tAttrs) { 4626 var tooltipLinker = $compile(template); 4627 4628 return function link(scope, element, attrs, tooltipCtrl) { 4629 var tooltip; 4630 var tooltipLinkedScope; 4631 var transitionTimeout; 4632 var showTimeout; 4633 var hideTimeout; 4634 var positionTimeout; 4635 var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false; 4636 var triggers = getTriggers(undefined); 4637 var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']); 4638 var ttScope = scope.$new(true); 4639 var repositionScheduled = false; 4640 var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false; 4641 var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false; 4642 var observers = []; 4643 var lastPlacement; 4644 4645 var positionTooltip = function() { 4646 // check if tooltip exists and is not empty 4647 if (!tooltip || !tooltip.html()) { return; } 4648 4649 if (!positionTimeout) { 4650 positionTimeout = $timeout(function() { 4651 var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody); 4652 tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' }); 4653 4654 if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) { 4655 tooltip.removeClass(lastPlacement.split('-')[0]); 4656 tooltip.addClass(ttPosition.placement.split('-')[0]); 4657 } 4658 4659 if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) { 4660 tooltip.removeClass(options.placementClassPrefix + lastPlacement); 4661 tooltip.addClass(options.placementClassPrefix + ttPosition.placement); 4662 } 4663 4664 // first time through tt element will have the 4665 // uib-position-measure class or if the placement 4666 // has changed we need to position the arrow. 4667 if (tooltip.hasClass('uib-position-measure')) { 4668 $position.positionArrow(tooltip, ttPosition.placement); 4669 tooltip.removeClass('uib-position-measure'); 4670 } else if (lastPlacement !== ttPosition.placement) { 4671 $position.positionArrow(tooltip, ttPosition.placement); 4672 } 4673 lastPlacement = ttPosition.placement; 4674 4675 positionTimeout = null; 4676 }, 0, false); 4677 } 4678 }; 4679 4680 // Set up the correct scope to allow transclusion later 4681 ttScope.origScope = scope; 4682 4683 // By default, the tooltip is not open. 4684 // TODO add ability to start tooltip opened 4685 ttScope.isOpen = false; 4686 openedTooltips.add(ttScope, { 4687 close: hide 4688 }); 4689 4690 function toggleTooltipBind() { 4691 if (!ttScope.isOpen) { 4692 showTooltipBind(); 4693 } else { 4694 hideTooltipBind(); 4695 } 4696 } 4697 4698 // Show the tooltip with delay if specified, otherwise show it immediately 4699 function showTooltipBind() { 4700 if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) { 4701 return; 4702 } 4703 4704 cancelHide(); 4705 prepareTooltip(); 4706 4707 if (ttScope.popupDelay) { 4708 // Do nothing if the tooltip was already scheduled to pop-up. 4709 // This happens if show is triggered multiple times before any hide is triggered. 4710 if (!showTimeout) { 4711 showTimeout = $timeout(show, ttScope.popupDelay, false); 4712 } 4713 } else { 4714 show(); 4715 } 4716 } 4717 4718 function hideTooltipBind() { 4719 cancelShow(); 4720 4721 if (ttScope.popupCloseDelay) { 4722 if (!hideTimeout) { 4723 hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false); 4724 } 4725 } else { 4726 hide(); 4727 } 4728 } 4729 4730 // Show the tooltip popup element. 4731 function show() { 4732 cancelShow(); 4733 cancelHide(); 4734 4735 // Don't show empty tooltips. 4736 if (!ttScope.content) { 4737 return angular.noop; 4738 } 4739 4740 createTooltip(); 4741 4742 // And show the tooltip. 4743 ttScope.$evalAsync(function() { 4744 ttScope.isOpen = true; 4745 assignIsOpen(true); 4746 positionTooltip(); 4747 }); 4748 } 4749 4750 function cancelShow() { 4751 if (showTimeout) { 4752 $timeout.cancel(showTimeout); 4753 showTimeout = null; 4754 } 4755 4756 if (positionTimeout) { 4757 $timeout.cancel(positionTimeout); 4758 positionTimeout = null; 4759 } 4760 } 4761 4762 // Hide the tooltip popup element. 4763 function hide() { 4764 if (!ttScope) { 4765 return; 4766 } 4767 4768 // First things first: we don't show it anymore. 4769 ttScope.$evalAsync(function() { 4770 if (ttScope) { 4771 ttScope.isOpen = false; 4772 assignIsOpen(false); 4773 // And now we remove it from the DOM. However, if we have animation, we 4774 // need to wait for it to expire beforehand. 4775 // FIXME: this is a placeholder for a port of the transitions library. 4776 // The fade transition in TWBS is 150ms. 4777 if (ttScope.animation) { 4778 if (!transitionTimeout) { 4779 transitionTimeout = $timeout(removeTooltip, 150, false); 4780 } 4781 } else { 4782 removeTooltip(); 4783 } 4784 } 4785 }); 4786 } 4787 4788 function cancelHide() { 4789 if (hideTimeout) { 4790 $timeout.cancel(hideTimeout); 4791 hideTimeout = null; 4792 } 4793 4794 if (transitionTimeout) { 4795 $timeout.cancel(transitionTimeout); 4796 transitionTimeout = null; 4797 } 4798 } 4799 4800 function createTooltip() { 4801 // There can only be one tooltip element per directive shown at once. 4802 if (tooltip) { 4803 return; 4804 } 4805 4806 tooltipLinkedScope = ttScope.$new(); 4807 tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) { 4808 if (appendToBody) { 4809 $document.find('body').append(tooltip); 4810 } else { 4811 element.after(tooltip); 4812 } 4813 }); 4814 4815 prepObservers(); 4816 } 4817 4818 function removeTooltip() { 4819 cancelShow(); 4820 cancelHide(); 4821 unregisterObservers(); 4822 4823 if (tooltip) { 4824 tooltip.remove(); 4825 tooltip = null; 4826 } 4827 if (tooltipLinkedScope) { 4828 tooltipLinkedScope.$destroy(); 4829 tooltipLinkedScope = null; 4830 } 4831 } 4832 4833 /** 4834 * Set the initial scope values. Once 4835 * the tooltip is created, the observers 4836 * will be added to keep things in sync. 4837 */ 4838 function prepareTooltip() { 4839 ttScope.title = attrs[prefix + 'Title']; 4840 if (contentParse) { 4841 ttScope.content = contentParse(scope); 4842 } else { 4843 ttScope.content = attrs[ttType]; 4844 } 4845 4846 ttScope.popupClass = attrs[prefix + 'Class']; 4847 ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement; 4848 var placement = $position.parsePlacement(ttScope.placement); 4849 lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0]; 4850 4851 var delay = parseInt(attrs[prefix + 'PopupDelay'], 10); 4852 var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10); 4853 ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay; 4854 ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay; 4855 } 4856 4857 function assignIsOpen(isOpen) { 4858 if (isOpenParse && angular.isFunction(isOpenParse.assign)) { 4859 isOpenParse.assign(scope, isOpen); 4860 } 4861 } 4862 4863 ttScope.contentExp = function() { 4864 return ttScope.content; 4865 }; 4866 4867 /** 4868 * Observe the relevant attributes. 4869 */ 4870 attrs.$observe('disabled', function(val) { 4871 if (val) { 4872 cancelShow(); 4873 } 4874 4875 if (val && ttScope.isOpen) { 4876 hide(); 4877 } 4878 }); 4879 4880 if (isOpenParse) { 4881 scope.$watch(isOpenParse, function(val) { 4882 if (ttScope && !val === ttScope.isOpen) { 4883 toggleTooltipBind(); 4884 } 4885 }); 4886 } 4887 4888 function prepObservers() { 4889 observers.length = 0; 4890 4891 if (contentParse) { 4892 observers.push( 4893 scope.$watch(contentParse, function(val) { 4894 ttScope.content = val; 4895 if (!val && ttScope.isOpen) { 4896 hide(); 4897 } 4898 }) 4899 ); 4900 4901 observers.push( 4902 tooltipLinkedScope.$watch(function() { 4903 if (!repositionScheduled) { 4904 repositionScheduled = true; 4905 tooltipLinkedScope.$$postDigest(function() { 4906 repositionScheduled = false; 4907 if (ttScope && ttScope.isOpen) { 4908 positionTooltip(); 4909 } 4910 }); 4911 } 4912 }) 4913 ); 4914 } else { 4915 observers.push( 4916 attrs.$observe(ttType, function(val) { 4917 ttScope.content = val; 4918 if (!val && ttScope.isOpen) { 4919 hide(); 4920 } else { 4921 positionTooltip(); 4922 } 4923 }) 4924 ); 4925 } 4926 4927 observers.push( 4928 attrs.$observe(prefix + 'Title', function(val) { 4929 ttScope.title = val; 4930 if (ttScope.isOpen) { 4931 positionTooltip(); 4932 } 4933 }) 4934 ); 4935 4936 observers.push( 4937 attrs.$observe(prefix + 'Placement', function(val) { 4938 ttScope.placement = val ? val : options.placement; 4939 if (ttScope.isOpen) { 4940 positionTooltip(); 4941 } 4942 }) 4943 ); 4944 } 4945 4946 function unregisterObservers() { 4947 if (observers.length) { 4948 angular.forEach(observers, function(observer) { 4949 observer(); 4950 }); 4951 observers.length = 0; 4952 } 4953 } 4954 4955 // hide tooltips/popovers for outsideClick trigger 4956 function bodyHideTooltipBind(e) { 4957 if (!ttScope || !ttScope.isOpen || !tooltip) { 4958 return; 4959 } 4960 // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked 4961 if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) { 4962 hideTooltipBind(); 4963 } 4964 } 4965 4966 var unregisterTriggers = function() { 4967 triggers.show.forEach(function(trigger) { 4968 if (trigger === 'outsideClick') { 4969 element.off('click', toggleTooltipBind); 4970 } else { 4971 element.off(trigger, showTooltipBind); 4972 element.off(trigger, toggleTooltipBind); 4973 } 4974 }); 4975 triggers.hide.forEach(function(trigger) { 4976 if (trigger === 'outsideClick') { 4977 $document.off('click', bodyHideTooltipBind); 4978 } else { 4979 element.off(trigger, hideTooltipBind); 4980 } 4981 }); 4982 }; 4983 4984 function prepTriggers() { 4985 var val = attrs[prefix + 'Trigger']; 4986 unregisterTriggers(); 4987 4988 triggers = getTriggers(val); 4989 4990 if (triggers.show !== 'none') { 4991 triggers.show.forEach(function(trigger, idx) { 4992 if (trigger === 'outsideClick') { 4993 element.on('click', toggleTooltipBind); 4994 $document.on('click', bodyHideTooltipBind); 4995 } else if (trigger === triggers.hide[idx]) { 4996 element.on(trigger, toggleTooltipBind); 4997 } else if (trigger) { 4998 element.on(trigger, showTooltipBind); 4999 element.on(triggers.hide[idx], hideTooltipBind); 5000 } 5001 5002 element.on('keypress', function(e) { 5003 if (e.which === 27) { 5004 hideTooltipBind(); 5005 } 5006 }); 5007 }); 5008 } 5009 } 5010 5011 prepTriggers(); 5012 5013 var animation = scope.$eval(attrs[prefix + 'Animation']); 5014 ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation; 5015 5016 var appendToBodyVal; 5017 var appendKey = prefix + 'AppendToBody'; 5018 if (appendKey in attrs && attrs[appendKey] === undefined) { 5019 appendToBodyVal = true; 5020 } else { 5021 appendToBodyVal = scope.$eval(attrs[appendKey]); 5022 } 5023 5024 appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody; 5025 5026 // Make sure tooltip is destroyed and removed. 5027 scope.$on('$destroy', function onDestroyTooltip() { 5028 unregisterTriggers(); 5029 removeTooltip(); 5030 openedTooltips.remove(ttScope); 5031 ttScope = null; 5032 }); 5033 }; 5034 } 5035 }; 5036 }; 5037 }]; 5038}) 5039 5040// This is mostly ngInclude code but with a custom scope 5041.directive('uibTooltipTemplateTransclude', [ 5042 '$animate', '$sce', '$compile', '$templateRequest', 5043function ($animate, $sce, $compile, $templateRequest) { 5044 return { 5045 link: function(scope, elem, attrs) { 5046 var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope); 5047 5048 var changeCounter = 0, 5049 currentScope, 5050 previousElement, 5051 currentElement; 5052 5053 var cleanupLastIncludeContent = function() { 5054 if (previousElement) { 5055 previousElement.remove(); 5056 previousElement = null; 5057 } 5058 5059 if (currentScope) { 5060 currentScope.$destroy(); 5061 currentScope = null; 5062 } 5063 5064 if (currentElement) { 5065 $animate.leave(currentElement).then(function() { 5066 previousElement = null; 5067 }); 5068 previousElement = currentElement; 5069 currentElement = null; 5070 } 5071 }; 5072 5073 scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) { 5074 var thisChangeId = ++changeCounter; 5075 5076 if (src) { 5077 //set the 2nd param to true to ignore the template request error so that the inner 5078 //contents and scope can be cleaned up. 5079 $templateRequest(src, true).then(function(response) { 5080 if (thisChangeId !== changeCounter) { return; } 5081 var newScope = origScope.$new(); 5082 var template = response; 5083 5084 var clone = $compile(template)(newScope, function(clone) { 5085 cleanupLastIncludeContent(); 5086 $animate.enter(clone, elem); 5087 }); 5088 5089 currentScope = newScope; 5090 currentElement = clone; 5091 5092 currentScope.$emit('$includeContentLoaded', src); 5093 }, function() { 5094 if (thisChangeId === changeCounter) { 5095 cleanupLastIncludeContent(); 5096 scope.$emit('$includeContentError', src); 5097 } 5098 }); 5099 scope.$emit('$includeContentRequested', src); 5100 } else { 5101 cleanupLastIncludeContent(); 5102 } 5103 }); 5104 5105 scope.$on('$destroy', cleanupLastIncludeContent); 5106 } 5107 }; 5108}]) 5109 5110/** 5111 * Note that it's intentional that these classes are *not* applied through $animate. 5112 * They must not be animated as they're expected to be present on the tooltip on 5113 * initialization. 5114 */ 5115.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) { 5116 return { 5117 restrict: 'A', 5118 link: function(scope, element, attrs) { 5119 // need to set the primary position so the 5120 // arrow has space during position measure. 5121 // tooltip.positionTooltip() 5122 if (scope.placement) { 5123 // // There are no top-left etc... classes 5124 // // in TWBS, so we need the primary position. 5125 var position = $uibPosition.parsePlacement(scope.placement); 5126 element.addClass(position[0]); 5127 } 5128 5129 if (scope.popupClass) { 5130 element.addClass(scope.popupClass); 5131 } 5132 5133 if (scope.animation()) { 5134 element.addClass(attrs.tooltipAnimationClass); 5135 } 5136 } 5137 }; 5138}]) 5139 5140.directive('uibTooltipPopup', function() { 5141 return { 5142 replace: true, 5143 scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 5144 templateUrl: 'uib/template/tooltip/tooltip-popup.html' 5145 }; 5146}) 5147 5148.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) { 5149 return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter'); 5150}]) 5151 5152.directive('uibTooltipTemplatePopup', function() { 5153 return { 5154 replace: true, 5155 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', 5156 originScope: '&' }, 5157 templateUrl: 'uib/template/tooltip/tooltip-template-popup.html' 5158 }; 5159}) 5160 5161.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) { 5162 return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', { 5163 useContentExp: true 5164 }); 5165}]) 5166 5167.directive('uibTooltipHtmlPopup', function() { 5168 return { 5169 replace: true, 5170 scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 5171 templateUrl: 'uib/template/tooltip/tooltip-html-popup.html' 5172 }; 5173}) 5174 5175.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) { 5176 return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', { 5177 useContentExp: true 5178 }); 5179}]); 5180 5181/** 5182 * The following features are still outstanding: popup delay, animation as a 5183 * function, placement as a function, inside, support for more triggers than 5184 * just mouse enter/leave, and selector delegatation. 5185 */ 5186angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip']) 5187 5188.directive('uibPopoverTemplatePopup', function() { 5189 return { 5190 replace: true, 5191 scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&', 5192 originScope: '&' }, 5193 templateUrl: 'uib/template/popover/popover-template.html' 5194 }; 5195}) 5196 5197.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) { 5198 return $uibTooltip('uibPopoverTemplate', 'popover', 'click', { 5199 useContentExp: true 5200 }); 5201}]) 5202 5203.directive('uibPopoverHtmlPopup', function() { 5204 return { 5205 replace: true, 5206 scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 5207 templateUrl: 'uib/template/popover/popover-html.html' 5208 }; 5209}) 5210 5211.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) { 5212 return $uibTooltip('uibPopoverHtml', 'popover', 'click', { 5213 useContentExp: true 5214 }); 5215}]) 5216 5217.directive('uibPopoverPopup', function() { 5218 return { 5219 replace: true, 5220 scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' }, 5221 templateUrl: 'uib/template/popover/popover.html' 5222 }; 5223}) 5224 5225.directive('uibPopover', ['$uibTooltip', function($uibTooltip) { 5226 return $uibTooltip('uibPopover', 'popover', 'click'); 5227}]); 5228 5229angular.module('ui.bootstrap.progressbar', []) 5230 5231.constant('uibProgressConfig', { 5232 animate: true, 5233 max: 100 5234}) 5235 5236.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) { 5237 var self = this, 5238 animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate; 5239 5240 this.bars = []; 5241 $scope.max = getMaxOrDefault(); 5242 5243 this.addBar = function(bar, element, attrs) { 5244 if (!animate) { 5245 element.css({'transition': 'none'}); 5246 } 5247 5248 this.bars.push(bar); 5249 5250 bar.max = getMaxOrDefault(); 5251 bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar'; 5252 5253 bar.$watch('value', function(value) { 5254 bar.recalculatePercentage(); 5255 }); 5256 5257 bar.recalculatePercentage = function() { 5258 var totalPercentage = self.bars.reduce(function(total, bar) { 5259 bar.percent = +(100 * bar.value / bar.max).toFixed(2); 5260 return total + bar.percent; 5261 }, 0); 5262 5263 if (totalPercentage > 100) { 5264 bar.percent -= totalPercentage - 100; 5265 } 5266 }; 5267 5268 bar.$on('$destroy', function() { 5269 element = null; 5270 self.removeBar(bar); 5271 }); 5272 }; 5273 5274 this.removeBar = function(bar) { 5275 this.bars.splice(this.bars.indexOf(bar), 1); 5276 this.bars.forEach(function (bar) { 5277 bar.recalculatePercentage(); 5278 }); 5279 }; 5280 5281 //$attrs.$observe('maxParam', function(maxParam) { 5282 $scope.$watch('maxParam', function(maxParam) { 5283 self.bars.forEach(function(bar) { 5284 bar.max = getMaxOrDefault(); 5285 bar.recalculatePercentage(); 5286 }); 5287 }); 5288 5289 function getMaxOrDefault () { 5290 return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max; 5291 } 5292}]) 5293 5294.directive('uibProgress', function() { 5295 return { 5296 replace: true, 5297 transclude: true, 5298 controller: 'UibProgressController', 5299 require: 'uibProgress', 5300 scope: { 5301 maxParam: '=?max' 5302 }, 5303 templateUrl: 'uib/template/progressbar/progress.html' 5304 }; 5305}) 5306 5307.directive('uibBar', function() { 5308 return { 5309 replace: true, 5310 transclude: true, 5311 require: '^uibProgress', 5312 scope: { 5313 value: '=', 5314 type: '@' 5315 }, 5316 templateUrl: 'uib/template/progressbar/bar.html', 5317 link: function(scope, element, attrs, progressCtrl) { 5318 progressCtrl.addBar(scope, element, attrs); 5319 } 5320 }; 5321}) 5322 5323.directive('uibProgressbar', function() { 5324 return { 5325 replace: true, 5326 transclude: true, 5327 controller: 'UibProgressController', 5328 scope: { 5329 value: '=', 5330 maxParam: '=?max', 5331 type: '@' 5332 }, 5333 templateUrl: 'uib/template/progressbar/progressbar.html', 5334 link: function(scope, element, attrs, progressCtrl) { 5335 progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title}); 5336 } 5337 }; 5338}); 5339 5340angular.module('ui.bootstrap.rating', []) 5341 5342.constant('uibRatingConfig', { 5343 max: 5, 5344 stateOn: null, 5345 stateOff: null, 5346 enableReset: true, 5347 titles : ['one', 'two', 'three', 'four', 'five'] 5348}) 5349 5350.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) { 5351 var ngModelCtrl = { $setViewValue: angular.noop }, 5352 self = this; 5353 5354 this.init = function(ngModelCtrl_) { 5355 ngModelCtrl = ngModelCtrl_; 5356 ngModelCtrl.$render = this.render; 5357 5358 ngModelCtrl.$formatters.push(function(value) { 5359 if (angular.isNumber(value) && value << 0 !== value) { 5360 value = Math.round(value); 5361 } 5362 5363 return value; 5364 }); 5365 5366 this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn; 5367 this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff; 5368 this.enableReset = angular.isDefined($attrs.enableReset) ? 5369 $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset; 5370 var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles; 5371 this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ? 5372 tmpTitles : ratingConfig.titles; 5373 5374 var ratingStates = angular.isDefined($attrs.ratingStates) ? 5375 $scope.$parent.$eval($attrs.ratingStates) : 5376 new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max); 5377 $scope.range = this.buildTemplateObjects(ratingStates); 5378 }; 5379 5380 this.buildTemplateObjects = function(states) { 5381 for (var i = 0, n = states.length; i < n; i++) { 5382 states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]); 5383 } 5384 return states; 5385 }; 5386 5387 this.getTitle = function(index) { 5388 if (index >= this.titles.length) { 5389 return index + 1; 5390 } 5391 5392 return this.titles[index]; 5393 }; 5394 5395 $scope.rate = function(value) { 5396 if (!$scope.readonly && value >= 0 && value <= $scope.range.length) { 5397 var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value; 5398 ngModelCtrl.$setViewValue(newViewValue); 5399 ngModelCtrl.$render(); 5400 } 5401 }; 5402 5403 $scope.enter = function(value) { 5404 if (!$scope.readonly) { 5405 $scope.value = value; 5406 } 5407 $scope.onHover({value: value}); 5408 }; 5409 5410 $scope.reset = function() { 5411 $scope.value = ngModelCtrl.$viewValue; 5412 $scope.onLeave(); 5413 }; 5414 5415 $scope.onKeydown = function(evt) { 5416 if (/(37|38|39|40)/.test(evt.which)) { 5417 evt.preventDefault(); 5418 evt.stopPropagation(); 5419 $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1)); 5420 } 5421 }; 5422 5423 this.render = function() { 5424 $scope.value = ngModelCtrl.$viewValue; 5425 $scope.title = self.getTitle($scope.value - 1); 5426 }; 5427}]) 5428 5429.directive('uibRating', function() { 5430 return { 5431 require: ['uibRating', 'ngModel'], 5432 scope: { 5433 readonly: '=?readOnly', 5434 onHover: '&', 5435 onLeave: '&' 5436 }, 5437 controller: 'UibRatingController', 5438 templateUrl: 'uib/template/rating/rating.html', 5439 replace: true
vendor: 7,013 bytes, lines 5439-5701
5439, 5440 link: function(scope, element, attrs, ctrls) { 5441 var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 5442 ratingCtrl.init(ngModelCtrl); 5443 } 5444 }; 5445}); 5446 5447angular.module('ui.bootstrap.tabs', []) 5448 5449.controller('UibTabsetController', ['$scope', function ($scope) { 5450 var ctrl = this, 5451 oldIndex; 5452 ctrl.tabs = []; 5453 5454 ctrl.select = function(index, evt) { 5455 if (!destroyed) { 5456 var previousIndex = findTabIndex(oldIndex); 5457 var previousSelected = ctrl.tabs[previousIndex]; 5458 if (previousSelected) { 5459 previousSelected.tab.onDeselect({ 5460 $event: evt, 5461 $selectedIndex: index 5462 }); 5463 if (evt && evt.isDefaultPrevented()) { 5464 return; 5465 } 5466 previousSelected.tab.active = false; 5467 } 5468 5469 var selected = ctrl.tabs[index]; 5470 if (selected) { 5471 selected.tab.onSelect({ 5472 $event: evt 5473 }); 5474 selected.tab.active = true; 5475 ctrl.active = selected.index; 5476 oldIndex = selected.index; 5477 } else if (!selected && angular.isDefined(oldIndex)) { 5478 ctrl.active = null; 5479 oldIndex = null; 5480 } 5481 } 5482 }; 5483 5484 ctrl.addTab = function addTab(tab) { 5485 ctrl.tabs.push({ 5486 tab: tab, 5487 index: tab.index 5488 }); 5489 ctrl.tabs.sort(function(t1, t2) { 5490 if (t1.index > t2.index) { 5491 return 1; 5492 } 5493 5494 if (t1.index < t2.index) { 5495 return -1; 5496 } 5497 5498 return 0; 5499 }); 5500 5501 if (tab.index === ctrl.active || !angular.isDefined(ctrl.active) && ctrl.tabs.length === 1) { 5502 var newActiveIndex = findTabIndex(tab.index); 5503 ctrl.select(newActiveIndex); 5504 } 5505 }; 5506 5507 ctrl.removeTab = function removeTab(tab) { 5508 var index; 5509 for (var i = 0; i < ctrl.tabs.length; i++) { 5510 if (ctrl.tabs[i].tab === tab) { 5511 index = i; 5512 break; 5513 } 5514 } 5515 5516 if (ctrl.tabs[index].index === ctrl.active) { 5517 var newActiveTabIndex = index === ctrl.tabs.length - 1 ? 5518 index - 1 : index + 1 % ctrl.tabs.length; 5519 ctrl.select(newActiveTabIndex); 5520 } 5521 5522 ctrl.tabs.splice(index, 1); 5523 }; 5524 5525 $scope.$watch('tabset.active', function(val) { 5526 if (angular.isDefined(val) && val !== oldIndex) { 5527 ctrl.select(findTabIndex(val)); 5528 } 5529 }); 5530 5531 var destroyed; 5532 $scope.$on('$destroy', function() { 5533 destroyed = true; 5534 }); 5535 5536 function findTabIndex(index) { 5537 for (var i = 0; i < ctrl.tabs.length; i++) { 5538 if (ctrl.tabs[i].index === index) { 5539 return i; 5540 } 5541 } 5542 } 5543}]) 5544 5545.directive('uibTabset', function() { 5546 return { 5547 transclude: true, 5548 replace: true, 5549 scope: {}, 5550 bindToController: { 5551 active: '=?', 5552 type: '@' 5553 }, 5554 controller: 'UibTabsetController', 5555 controllerAs: 'tabset', 5556 templateUrl: function(element, attrs) { 5557 return attrs.templateUrl || 'uib/template/tabs/tabset.html'; 5558 }, 5559 link: function(scope, element, attrs) { 5560 scope.vertical = angular.isDefined(attrs.vertical) ? 5561 scope.$parent.$eval(attrs.vertical) : false; 5562 scope.justified = angular.isDefined(attrs.justified) ? 5563 scope.$parent.$eval(attrs.justified) : false; 5564 } 5565 }; 5566}) 5567 5568.directive('uibTab', ['$parse', function($parse) { 5569 return { 5570 require: '^uibTabset', 5571 replace: true, 5572 templateUrl: function(element, attrs) { 5573 return attrs.templateUrl || 'uib/template/tabs/tab.html'; 5574 }, 5575 transclude: true, 5576 scope: { 5577 heading: '@', 5578 index: '=?', 5579 classes: '@?', 5580 onSelect: '&select', //This callback is called in contentHeadingTransclude 5581 //once it inserts the tab's content into the dom 5582 onDeselect: '&deselect' 5583 }, 5584 controller: function() { 5585 //Empty controller so other directives can require being 'under' a tab 5586 }, 5587 controllerAs: 'tab', 5588 link: function(scope, elm, attrs, tabsetCtrl, transclude) { 5589 scope.disabled = false; 5590 if (attrs.disable) { 5591 scope.$parent.$watch($parse(attrs.disable), function(value) { 5592 scope.disabled = !! value; 5593 }); 5594 } 5595 5596 if (angular.isUndefined(attrs.index)) { 5597 if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) { 5598 scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1; 5599 } else { 5600 scope.index = 0; 5601 } 5602 } 5603 5604 if (angular.isUndefined(attrs.classes)) { 5605 scope.classes = ''; 5606 } 5607 5608 scope.select = function(evt) { 5609 if (!scope.disabled) { 5610 var index; 5611 for (var i = 0; i < tabsetCtrl.tabs.length; i++) { 5612 if (tabsetCtrl.tabs[i].tab === scope) { 5613 index = i; 5614 break; 5615 } 5616 } 5617 5618 tabsetCtrl.select(index, evt); 5619 } 5620 }; 5621 5622 tabsetCtrl.addTab(scope); 5623 scope.$on('$destroy', function() { 5624 tabsetCtrl.removeTab(scope); 5625 }); 5626 5627 //We need to transclude later, once the content container is ready. 5628 //when this link happens, we're inside a tab heading. 5629 scope.$transcludeFn = transclude; 5630 } 5631 }; 5632}]) 5633 5634.directive('uibTabHeadingTransclude', function() { 5635 return { 5636 restrict: 'A', 5637 require: '^uibTab', 5638 link: function(scope, elm) { 5639 scope.$watch('headingElement', function updateHeadingElement(heading) { 5640 if (heading) { 5641 elm.html(''); 5642 elm.append(heading); 5643 } 5644 }); 5645 } 5646 }; 5647}) 5648 5649.directive('uibTabContentTransclude', function() { 5650 return { 5651 restrict: 'A', 5652 require: '^uibTabset', 5653 link: function(scope, elm, attrs) { 5654 var tab = scope.$eval(attrs.uibTabContentTransclude).tab; 5655 5656 //Now our tab is ready to be transcluded: both the tab heading area 5657 //and the tab content area are loaded. Transclude 'em both. 5658 tab.$transcludeFn(tab.$parent, function(contents) { 5659 angular.forEach(contents, function(node) { 5660 if (isTabHeading(node)) { 5661 //Let tabHeadingTransclude know. 5662 tab.headingElement = node; 5663 } else { 5664 elm.append(node); 5665 } 5666 }); 5667 }); 5668 } 5669 }; 5670 5671 function isTabHeading(node) { 5672 return node.tagName && ( 5673 node.hasAttribute('uib-tab-heading') || 5674 node.hasAttribute('data-uib-tab-heading') || 5675 node.hasAttribute('x-uib-tab-heading') || 5676 node.tagName.toLowerCase() === 'uib-tab-heading' || 5677 node.tagName.toLowerCase() === 'data-uib-tab-heading' || 5678 node.tagName.toLowerCase() === 'x-uib-tab-heading' || 5679 node.tagName.toLowerCase() === 'uib:tab-heading' 5680 ); 5681 } 5682}); 5683 5684angular.module('ui.bootstrap.timepicker', []) 5685 5686.constant('uibTimepickerConfig', { 5687 hourStep: 1, 5688 minuteStep: 1, 5689 secondStep: 1, 5690 showMeridian: true, 5691 showSeconds: false, 5692 meridians: null, 5693 readonlyInput: false, 5694 mousewheel: true, 5695 arrowkeys: true, 5696 showSpinners: true, 5697 templateUrl: 'uib/template/timepicker/timepicker.html' 5698}) 5699 5700.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) { 5701 var
vendor: 41,811 bytes, lines 5701-6944
5701selected = new Date(), 5702 watchers = [], 5703 ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl 5704 meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS, 5705 padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true; 5706 5707 $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0; 5708 $element.removeAttr('tabindex'); 5709 5710 this.init = function(ngModelCtrl_, inputs) { 5711 ngModelCtrl = ngModelCtrl_; 5712 ngModelCtrl.$render = this.render; 5713 5714 ngModelCtrl.$formatters.unshift(function(modelValue) { 5715 return modelValue ? new Date(modelValue) : null; 5716 }); 5717 5718 var hoursInputEl = inputs.eq(0), 5719 minutesInputEl = inputs.eq(1), 5720 secondsInputEl = inputs.eq(2); 5721 5722 var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel; 5723 5724 if (mousewheel) { 5725 this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl); 5726 } 5727 5728 var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys; 5729 if (arrowkeys) { 5730 this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl); 5731 } 5732 5733 $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput; 5734 this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl); 5735 }; 5736 5737 var hourStep = timepickerConfig.hourStep; 5738 if ($attrs.hourStep) { 5739 watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) { 5740 hourStep = +value; 5741 })); 5742 } 5743 5744 var minuteStep = timepickerConfig.minuteStep; 5745 if ($attrs.minuteStep) { 5746 watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) { 5747 minuteStep = +value; 5748 })); 5749 } 5750 5751 var min; 5752 watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) { 5753 var dt = new Date(value); 5754 min = isNaN(dt) ? undefined : dt; 5755 })); 5756 5757 var max; 5758 watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) { 5759 var dt = new Date(value); 5760 max = isNaN(dt) ? undefined : dt; 5761 })); 5762 5763 var disabled = false; 5764 if ($attrs.ngDisabled) { 5765 watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) { 5766 disabled = value; 5767 })); 5768 } 5769 5770 $scope.noIncrementHours = function() { 5771 var incrementedSelected = addMinutes(selected, hourStep * 60); 5772 return disabled || incrementedSelected > max || 5773 incrementedSelected < selected && incrementedSelected < min; 5774 }; 5775 5776 $scope.noDecrementHours = function() { 5777 var decrementedSelected = addMinutes(selected, -hourStep * 60); 5778 return disabled || decrementedSelected < min || 5779 decrementedSelected > selected && decrementedSelected > max; 5780 }; 5781 5782 $scope.noIncrementMinutes = function() { 5783 var incrementedSelected = addMinutes(selected, minuteStep); 5784 return disabled || incrementedSelected > max || 5785 incrementedSelected < selected && incrementedSelected < min; 5786 }; 5787 5788 $scope.noDecrementMinutes = function() { 5789 var decrementedSelected = addMinutes(selected, -minuteStep); 5790 return disabled || decrementedSelected < min || 5791 decrementedSelected > selected && decrementedSelected > max; 5792 }; 5793 5794 $scope.noIncrementSeconds = function() { 5795 var incrementedSelected = addSeconds(selected, secondStep); 5796 return disabled || incrementedSelected > max || 5797 incrementedSelected < selected && incrementedSelected < min; 5798 }; 5799 5800 $scope.noDecrementSeconds = function() { 5801 var decrementedSelected = addSeconds(selected, -secondStep); 5802 return disabled || decrementedSelected < min || 5803 decrementedSelected > selected && decrementedSelected > max; 5804 }; 5805 5806 $scope.noToggleMeridian = function() { 5807 if (selected.getHours() < 12) { 5808 return disabled || addMinutes(selected, 12 * 60) > max; 5809 } 5810 5811 return disabled || addMinutes(selected, -12 * 60) < min; 5812 }; 5813 5814 var secondStep = timepickerConfig.secondStep; 5815 if ($attrs.secondStep) { 5816 watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) { 5817 secondStep = +value; 5818 })); 5819 } 5820 5821 $scope.showSeconds = timepickerConfig.showSeconds; 5822 if ($attrs.showSeconds) { 5823 watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) { 5824 $scope.showSeconds = !!value; 5825 })); 5826 } 5827 5828 // 12H / 24H mode 5829 $scope.showMeridian = timepickerConfig.showMeridian; 5830 if ($attrs.showMeridian) { 5831 watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) { 5832 $scope.showMeridian = !!value; 5833 5834 if (ngModelCtrl.$error.time) { 5835 // Evaluate from template 5836 var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate(); 5837 if (angular.isDefined(hours) && angular.isDefined(minutes)) { 5838 selected.setHours(hours); 5839 refresh(); 5840 } 5841 } else { 5842 updateTemplate(); 5843 } 5844 })); 5845 } 5846 5847 // Get $scope.hours in 24H mode if valid 5848 function getHoursFromTemplate() { 5849 var hours = +$scope.hours; 5850 var valid = $scope.showMeridian ? hours > 0 && hours < 13 : 5851 hours >= 0 && hours < 24; 5852 if (!valid || $scope.hours === '') { 5853 return undefined; 5854 } 5855 5856 if ($scope.showMeridian) { 5857 if (hours === 12) { 5858 hours = 0; 5859 } 5860 if ($scope.meridian === meridians[1]) { 5861 hours = hours + 12; 5862 } 5863 } 5864 return hours; 5865 } 5866 5867 function getMinutesFromTemplate() { 5868 var minutes = +$scope.minutes; 5869 var valid = minutes >= 0 && minutes < 60; 5870 if (!valid || $scope.minutes === '') { 5871 return undefined; 5872 } 5873 return minutes; 5874 } 5875 5876 function getSecondsFromTemplate() { 5877 var seconds = +$scope.seconds; 5878 return seconds >= 0 && seconds < 60 ? seconds : undefined; 5879 } 5880 5881 function pad(value, noPad) { 5882 if (value === null) { 5883 return ''; 5884 } 5885 5886 return angular.isDefined(value) && value.toString().length < 2 && !noPad ? 5887 '0' + value : value.toString(); 5888 } 5889 5890 // Respond on mousewheel spin 5891 this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { 5892 var isScrollingUp = function(e) { 5893 if (e.originalEvent) { 5894 e = e.originalEvent; 5895 } 5896 //pick correct delta variable depending on event 5897 var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY; 5898 return e.detail || delta > 0; 5899 }; 5900 5901 hoursInputEl.bind('mousewheel wheel', function(e) { 5902 if (!disabled) { 5903 $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours()); 5904 } 5905 e.preventDefault(); 5906 }); 5907 5908 minutesInputEl.bind('mousewheel wheel', function(e) { 5909 if (!disabled) { 5910 $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes()); 5911 } 5912 e.preventDefault(); 5913 }); 5914 5915 secondsInputEl.bind('mousewheel wheel', function(e) { 5916 if (!disabled) { 5917 $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds()); 5918 } 5919 e.preventDefault(); 5920 }); 5921 }; 5922 5923 // Respond on up/down arrowkeys 5924 this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { 5925 hoursInputEl.bind('keydown', function(e) { 5926 if (!disabled) { 5927 if (e.which === 38) { // up 5928 e.preventDefault(); 5929 $scope.incrementHours(); 5930 $scope.$apply(); 5931 } else if (e.which === 40) { // down 5932 e.preventDefault(); 5933 $scope.decrementHours(); 5934 $scope.$apply(); 5935 } 5936 } 5937 }); 5938 5939 minutesInputEl.bind('keydown', function(e) { 5940 if (!disabled) { 5941 if (e.which === 38) { // up 5942 e.preventDefault(); 5943 $scope.incrementMinutes(); 5944 $scope.$apply(); 5945 } else if (e.which === 40) { // down 5946 e.preventDefault(); 5947 $scope.decrementMinutes(); 5948 $scope.$apply(); 5949 } 5950 } 5951 }); 5952 5953 secondsInputEl.bind('keydown', function(e) { 5954 if (!disabled) { 5955 if (e.which === 38) { // up 5956 e.preventDefault(); 5957 $scope.incrementSeconds(); 5958 $scope.$apply(); 5959 } else if (e.which === 40) { // down 5960 e.preventDefault(); 5961 $scope.decrementSeconds(); 5962 $scope.$apply(); 5963 } 5964 } 5965 }); 5966 }; 5967 5968 this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) { 5969 if ($scope.readonlyInput) { 5970 $scope.updateHours = angular.noop; 5971 $scope.updateMinutes = angular.noop; 5972 $scope.updateSeconds = angular.noop; 5973 return; 5974 } 5975 5976 var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) { 5977 ngModelCtrl.$setViewValue(null); 5978 ngModelCtrl.$setValidity('time', false); 5979 if (angular.isDefined(invalidHours)) { 5980 $scope.invalidHours = invalidHours; 5981 } 5982 5983 if (angular.isDefined(invalidMinutes)) { 5984 $scope.invalidMinutes = invalidMinutes; 5985 } 5986 5987 if (angular.isDefined(invalidSeconds)) { 5988 $scope.invalidSeconds = invalidSeconds; 5989 } 5990 }; 5991 5992 $scope.updateHours = function() { 5993 var hours = getHoursFromTemplate(), 5994 minutes = getMinutesFromTemplate(); 5995 5996 ngModelCtrl.$setDirty(); 5997 5998 if (angular.isDefined(hours) && angular.isDefined(minutes)) { 5999 selected.setHours(hours); 6000 selected.setMinutes(minutes); 6001 if (selected < min || selected > max) { 6002 invalidate(true); 6003 } else { 6004 refresh('h'); 6005 } 6006 } else { 6007 invalidate(true); 6008 } 6009 }; 6010 6011 hoursInputEl.bind('blur', function(e) { 6012 ngModelCtrl.$setTouched(); 6013 if (modelIsEmpty()) { 6014 makeValid(); 6015 } else if ($scope.hours === null || $scope.hours === '') { 6016 invalidate(true); 6017 } else if (!$scope.invalidHours && $scope.hours < 10) { 6018 $scope.$apply(function() { 6019 $scope.hours = pad($scope.hours, !padHours); 6020 }); 6021 } 6022 }); 6023 6024 $scope.updateMinutes = function() { 6025 var minutes = getMinutesFromTemplate(), 6026 hours = getHoursFromTemplate(); 6027 6028 ngModelCtrl.$setDirty(); 6029 6030 if (angular.isDefined(minutes) && angular.isDefined(hours)) { 6031 selected.setHours(hours); 6032 selected.setMinutes(minutes); 6033 if (selected < min || selected > max) { 6034 invalidate(undefined, true); 6035 } else { 6036 refresh('m'); 6037 } 6038 } else { 6039 invalidate(undefined, true); 6040 } 6041 }; 6042 6043 minutesInputEl.bind('blur', function(e) { 6044 ngModelCtrl.$setTouched(); 6045 if (modelIsEmpty()) { 6046 makeValid(); 6047 } else if ($scope.minutes === null) { 6048 invalidate(undefined, true); 6049 } else if (!$scope.invalidMinutes && $scope.minutes < 10) { 6050 $scope.$apply(function() { 6051 $scope.minutes = pad($scope.minutes); 6052 }); 6053 } 6054 }); 6055 6056 $scope.updateSeconds = function() { 6057 var seconds = getSecondsFromTemplate(); 6058 6059 ngModelCtrl.$setDirty(); 6060 6061 if (angular.isDefined(seconds)) { 6062 selected.setSeconds(seconds); 6063 refresh('s'); 6064 } else { 6065 invalidate(undefined, undefined, true); 6066 } 6067 }; 6068 6069 secondsInputEl.bind('blur', function(e) { 6070 if (modelIsEmpty()) { 6071 makeValid(); 6072 } else if (!$scope.invalidSeconds && $scope.seconds < 10) { 6073 $scope.$apply( function() { 6074 $scope.seconds = pad($scope.seconds); 6075 }); 6076 } 6077 }); 6078 6079 }; 6080 6081 this.render = function() { 6082 var date = ngModelCtrl.$viewValue; 6083 6084 if (isNaN(date)) { 6085 ngModelCtrl.$setValidity('time', false); 6086 $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.'); 6087 } else { 6088 if (date) { 6089 selected = date; 6090 } 6091 6092 if (selected < min || selected > max) { 6093 ngModelCtrl.$setValidity('time', false); 6094 $scope.invalidHours = true; 6095 $scope.invalidMinutes = true; 6096 } else { 6097 makeValid(); 6098 } 6099 updateTemplate(); 6100 } 6101 }; 6102 6103 // Call internally when we know that model is valid. 6104 function refresh(keyboardChange) { 6105 makeValid(); 6106 ngModelCtrl.$setViewValue(new Date(selected)); 6107 updateTemplate(keyboardChange); 6108 } 6109 6110 function makeValid() { 6111 ngModelCtrl.$setValidity('time', true); 6112 $scope.invalidHours = false; 6113 $scope.invalidMinutes = false; 6114 $scope.invalidSeconds = false; 6115 } 6116 6117 function updateTemplate(keyboardChange) { 6118 if (!ngModelCtrl.$modelValue) { 6119 $scope.hours = null; 6120 $scope.minutes = null; 6121 $scope.seconds = null; 6122 $scope.meridian = meridians[0]; 6123 } else { 6124 var hours = selected.getHours(), 6125 minutes = selected.getMinutes(), 6126 seconds = selected.getSeconds(); 6127 6128 if ($scope.showMeridian) { 6129 hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system 6130 } 6131 6132 $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours); 6133 if (keyboardChange !== 'm') { 6134 $scope.minutes = pad(minutes); 6135 } 6136 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; 6137 6138 if (keyboardChange !== 's') { 6139 $scope.seconds = pad(seconds); 6140 } 6141 $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1]; 6142 } 6143 } 6144 6145 function addSecondsToSelected(seconds) { 6146 selected = addSeconds(selected, seconds); 6147 refresh(); 6148 } 6149 6150 function addMinutes(selected, minutes) { 6151 return addSeconds(selected, minutes*60); 6152 } 6153 6154 function addSeconds(date, seconds) { 6155 var dt = new Date(date.getTime() + seconds * 1000); 6156 var newDate = new Date(date); 6157 newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds()); 6158 return newDate; 6159 } 6160 6161 function modelIsEmpty() { 6162 return ($scope.hours === null || $scope.hours === '') && 6163 ($scope.minutes === null || $scope.minutes === '') && 6164 (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === '')); 6165 } 6166 6167 $scope.showSpinners = angular.isDefined($attrs.showSpinners) ? 6168 $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners; 6169 6170 $scope.incrementHours = function() { 6171 if (!$scope.noIncrementHours()) { 6172 addSecondsToSelected(hourStep * 60 * 60); 6173 } 6174 }; 6175 6176 $scope.decrementHours = function() { 6177 if (!$scope.noDecrementHours()) { 6178 addSecondsToSelected(-hourStep * 60 * 60); 6179 } 6180 }; 6181 6182 $scope.incrementMinutes = function() { 6183 if (!$scope.noIncrementMinutes()) { 6184 addSecondsToSelected(minuteStep * 60); 6185 } 6186 }; 6187 6188 $scope.decrementMinutes = function() { 6189 if (!$scope.noDecrementMinutes()) { 6190 addSecondsToSelected(-minuteStep * 60); 6191 } 6192 }; 6193 6194 $scope.incrementSeconds = function() { 6195 if (!$scope.noIncrementSeconds()) { 6196 addSecondsToSelected(secondStep); 6197 } 6198 }; 6199 6200 $scope.decrementSeconds = function() { 6201 if (!$scope.noDecrementSeconds()) { 6202 addSecondsToSelected(-secondStep); 6203 } 6204 }; 6205 6206 $scope.toggleMeridian = function() { 6207 var minutes = getMinutesFromTemplate(), 6208 hours = getHoursFromTemplate(); 6209 6210 if (!$scope.noToggleMeridian()) { 6211 if (angular.isDefined(minutes) && angular.isDefined(hours)) { 6212 addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60)); 6213 } else { 6214 $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0]; 6215 } 6216 } 6217 }; 6218 6219 $scope.blur = function() { 6220 ngModelCtrl.$setTouched(); 6221 }; 6222 6223 $scope.$on('$destroy', function() { 6224 while (watchers.length) { 6225 watchers.shift()(); 6226 } 6227 }); 6228}]) 6229 6230.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) { 6231 return { 6232 require: ['uibTimepicker', '?^ngModel'], 6233 controller: 'UibTimepickerController', 6234 controllerAs: 'timepicker', 6235 replace: true, 6236 scope: {}, 6237 templateUrl: function(element, attrs) { 6238 return attrs.templateUrl || uibTimepickerConfig.templateUrl; 6239 }, 6240 link: function(scope, element, attrs, ctrls) { 6241 var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1]; 6242 6243 if (ngModelCtrl) { 6244 timepickerCtrl.init(ngModelCtrl, element.find('input')); 6245 } 6246 } 6247 }; 6248}]); 6249 6250angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position']) 6251 6252/** 6253 * A helper service that can parse typeahead's syntax (string provided by users) 6254 * Extracted to a separate service for ease of unit testing 6255 */ 6256 .factory('uibTypeaheadParser', ['$parse', function($parse) { 6257 // 00000111000000000000022200000000000000003333333333333330000000000044000 6258 var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/; 6259 return { 6260 parse: function(input) { 6261 var match = input.match(TYPEAHEAD_REGEXP); 6262 if (!match) { 6263 throw new Error( 6264 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' + 6265 ' but got "' + input + '".'); 6266 } 6267 6268 return { 6269 itemName: match[3], 6270 source: $parse(match[4]), 6271 viewMapper: $parse(match[2] || match[1]), 6272 modelMapper: $parse(match[1]) 6273 }; 6274 } 6275 }; 6276 }]) 6277 6278 .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser', 6279 function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) { 6280 var HOT_KEYS = [9, 13, 27, 38, 40]; 6281 var eventDebounceTime = 200; 6282 var modelCtrl, ngModelOptions; 6283 //SUPPORTED ATTRIBUTES (OPTIONS) 6284 6285 //minimal no of characters that needs to be entered before typeahead kicks-in 6286 var minLength = originalScope.$eval(attrs.typeaheadMinLength); 6287 if (!minLength && minLength !== 0) { 6288 minLength = 1; 6289 } 6290 6291 originalScope.$watch(attrs.typeaheadMinLength, function (newVal) { 6292 minLength = !newVal && newVal !== 0 ? 1 : newVal; 6293 }); 6294 6295 //minimal wait time after last character typed before typeahead kicks-in 6296 var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; 6297 6298 //should it restrict model values to the ones selected from the popup only? 6299 var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false; 6300 originalScope.$watch(attrs.typeaheadEditable, function (newVal) { 6301 isEditable = newVal !== false; 6302 }); 6303 6304 //binding to a variable that indicates if matches are being retrieved asynchronously 6305 var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; 6306 6307 //a function to determine if an event should cause selection 6308 var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) { 6309 var evt = vals.$event; 6310 return evt.which === 13 || evt.which === 9; 6311 }; 6312 6313 //a callback executed when a match is selected 6314 var onSelectCallback = $parse(attrs.typeaheadOnSelect); 6315 6316 //should it select highlighted popup value when losing focus? 6317 var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false; 6318 6319 //binding to a variable that indicates if there were no results after the query is completed 6320 var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop; 6321 6322 var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined; 6323 6324 var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false; 6325 6326 var appendTo = attrs.typeaheadAppendTo ? 6327 originalScope.$eval(attrs.typeaheadAppendTo) : null; 6328 6329 var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false; 6330 6331 //If input matches an item of the list exactly, select it automatically 6332 var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false; 6333 6334 //binding to a variable that indicates if dropdown is open 6335 var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop; 6336 6337 var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false; 6338 6339 //INTERNAL VARIABLES 6340 6341 //model setter executed upon match selection 6342 var parsedModel = $parse(attrs.ngModel); 6343 var invokeModelSetter = $parse(attrs.ngModel + '($$$p)'); 6344 var $setModelValue = function(scope, newValue) { 6345 if (angular.isFunction(parsedModel(originalScope)) && 6346 ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) { 6347 return invokeModelSetter(scope, {$$$p: newValue}); 6348 } 6349 6350 return parsedModel.assign(scope, newValue); 6351 }; 6352 6353 //expressions used by typeahead 6354 var parserResult = typeaheadParser.parse(attrs.uibTypeahead); 6355 6356 var hasFocus; 6357 6358 //Used to avoid bug in iOS webview where iOS keyboard does not fire 6359 //mousedown & mouseup events 6360 //Issue #3699 6361 var selected; 6362 6363 //create a child scope for the typeahead directive so we are not polluting original scope 6364 //with typeahead-specific data (matches, query etc.) 6365 var scope = originalScope.$new(); 6366 var offDestroy = originalScope.$on('$destroy', function() { 6367 scope.$destroy(); 6368 }); 6369 scope.$on('$destroy', offDestroy); 6370 6371 // WAI-ARIA 6372 var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000); 6373 element.attr({ 6374 'aria-autocomplete': 'list', 6375 'aria-expanded': false, 6376 'aria-owns': popupId 6377 }); 6378 6379 var inputsContainer, hintInputElem; 6380 //add read-only input to show hint 6381 if (showHint) { 6382 inputsContainer = angular.element('<div></div>'); 6383 inputsContainer.css('position', 'relative'); 6384 element.after(inputsContainer); 6385 hintInputElem = element.clone(); 6386 hintInputElem.attr('placeholder', ''); 6387 hintInputElem.attr('tabindex', '-1'); 6388 hintInputElem.val(''); 6389 hintInputElem.css({ 6390 'position': 'absolute', 6391 'top': '0px', 6392 'left': '0px', 6393 'border-color': 'transparent', 6394 'box-shadow': 'none', 6395 'opacity': 1, 6396 'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)', 6397 'color': '#999' 6398 }); 6399 element.css({ 6400 'position': 'relative', 6401 'vertical-align': 'top', 6402 'background-color': 'transparent' 6403 }); 6404 inputsContainer.append(hintInputElem); 6405 hintInputElem.after(element); 6406 } 6407 6408 //pop-up element used to display matches 6409 var popUpEl = angular.element('<div uib-typeahead-popup></div>'); 6410 popUpEl.attr({ 6411 id: popupId, 6412 matches: 'matches', 6413 active: 'activeIdx', 6414 select: 'select(activeIdx, evt)', 6415 'move-in-progress': 'moveInProgress', 6416 query: 'query', 6417 position: 'position', 6418 'assign-is-open': 'assignIsOpen(isOpen)', 6419 debounce: 'debounceUpdate' 6420 }); 6421 //custom item template 6422 if (angular.isDefined(attrs.typeaheadTemplateUrl)) { 6423 popUpEl.attr('template-url', attrs.typeaheadTemplateUrl); 6424 } 6425 6426 if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) { 6427 popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl); 6428 } 6429 6430 var resetHint = function() { 6431 if (showHint) { 6432 hintInputElem.val(''); 6433 } 6434 }; 6435 6436 var resetMatches = function() { 6437 scope.matches = []; 6438 scope.activeIdx = -1; 6439 element.attr('aria-expanded', false); 6440 resetHint(); 6441 }; 6442 6443 var getMatchId = function(index) { 6444 return popupId + '-option-' + index; 6445 }; 6446 6447 // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead. 6448 // This attribute is added or removed automatically when the `activeIdx` changes. 6449 scope.$watch('activeIdx', function(index) { 6450 if (index < 0) { 6451 element.removeAttr('aria-activedescendant'); 6452 } else { 6453 element.attr('aria-activedescendant', getMatchId(index)); 6454 } 6455 }); 6456 6457 var inputIsExactMatch = function(inputValue, index) { 6458 if (scope.matches.length > index && inputValue) { 6459 return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase(); 6460 } 6461 6462 return false; 6463 }; 6464 6465 var getMatchesAsync = function(inputValue, evt) { 6466 var locals = {$viewValue: inputValue}; 6467 isLoadingSetter(originalScope, true); 6468 isNoResultsSetter(originalScope, false); 6469 $q.when(parserResult.source(originalScope, locals)).then(function(matches) { 6470 //it might happen that several async queries were in progress if a user were typing fast 6471 //but we are interested only in responses that correspond to the current view value 6472 var onCurrentRequest = inputValue === modelCtrl.$viewValue; 6473 if (onCurrentRequest && hasFocus) { 6474 if (matches && matches.length > 0) { 6475 scope.activeIdx = focusFirst ? 0 : -1; 6476 isNoResultsSetter(originalScope, false); 6477 scope.matches.length = 0; 6478 6479 //transform labels 6480 for (var i = 0; i < matches.length; i++) { 6481 locals[parserResult.itemName] = matches[i]; 6482 scope.matches.push({ 6483 id: getMatchId(i), 6484 label: parserResult.viewMapper(scope, locals), 6485 model: matches[i] 6486 }); 6487 } 6488 6489 scope.query = inputValue; 6490 //position pop-up with matches - we need to re-calculate its position each time we are opening a window 6491 //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page 6492 //due to other elements being rendered 6493 recalculatePosition(); 6494 6495 element.attr('aria-expanded', true); 6496 6497 //Select the single remaining option if user input matches 6498 if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) { 6499 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { 6500 $$debounce(function() { 6501 scope.select(0, evt); 6502 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); 6503 } else { 6504 scope.select(0, evt); 6505 } 6506 } 6507 6508 if (showHint) { 6509 var firstLabel = scope.matches[0].label; 6510 if (angular.isString(inputValue) && 6511 inputValue.length > 0 && 6512 firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) { 6513 hintInputElem.val(inputValue + firstLabel.slice(inputValue.length)); 6514 } else { 6515 hintInputElem.val(''); 6516 } 6517 } 6518 } else { 6519 resetMatches(); 6520 isNoResultsSetter(originalScope, true); 6521 } 6522 } 6523 if (onCurrentRequest) { 6524 isLoadingSetter(originalScope, false); 6525 } 6526 }, function() { 6527 resetMatches(); 6528 isLoadingSetter(originalScope, false); 6529 isNoResultsSetter(originalScope, true); 6530 }); 6531 }; 6532 6533 // bind events only if appendToBody params exist - performance feature 6534 if (appendToBody) { 6535 angular.element($window).on('resize', fireRecalculating); 6536 $document.find('body').on('scroll', fireRecalculating); 6537 } 6538 6539 // Declare the debounced function outside recalculating for 6540 // proper debouncing 6541 var debouncedRecalculate = $$debounce(function() { 6542 // if popup is visible 6543 if (scope.matches.length) { 6544 recalculatePosition(); 6545 } 6546 6547 scope.moveInProgress = false; 6548 }, eventDebounceTime); 6549 6550 // Default progress type 6551 scope.moveInProgress = false; 6552 6553 function fireRecalculating() { 6554 if (!scope.moveInProgress) { 6555 scope.moveInProgress = true; 6556 scope.$digest(); 6557 } 6558 6559 debouncedRecalculate(); 6560 } 6561 6562 // recalculate actual position and set new values to scope 6563 // after digest loop is popup in right position 6564 function recalculatePosition() { 6565 scope.position = appendToBody ? $position.offset(element) : $position.position(element); 6566 scope.position.top += element.prop('offsetHeight'); 6567 } 6568 6569 //we need to propagate user's query so we can higlight matches 6570 scope.query = undefined; 6571 6572 //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later 6573 var timeoutPromise; 6574 6575 var scheduleSearchWithTimeout = function(inputValue) { 6576 timeoutPromise = $timeout(function() { 6577 getMatchesAsync(inputValue); 6578 }, waitTime); 6579 }; 6580 6581 var cancelPreviousTimeout = function() { 6582 if (timeoutPromise) { 6583 $timeout.cancel(timeoutPromise); 6584 } 6585 }; 6586 6587 resetMatches(); 6588 6589 scope.assignIsOpen = function (isOpen) { 6590 isOpenSetter(originalScope, isOpen); 6591 }; 6592 6593 scope.select = function(activeIdx, evt) { 6594 //called from within the $digest() cycle 6595 var locals = {}; 6596 var model, item; 6597 6598 selected = true; 6599 locals[parserResult.itemName] = item = scope.matches[activeIdx].model; 6600 model = parserResult.modelMapper(originalScope, locals); 6601 $setModelValue(originalScope, model); 6602 modelCtrl.$setValidity('editable', true); 6603 modelCtrl.$setValidity('parse', true); 6604 6605 onSelectCallback(originalScope, { 6606 $item: item, 6607 $model: model, 6608 $label: parserResult.viewMapper(originalScope, locals), 6609 $event: evt 6610 }); 6611 6612 resetMatches(); 6613 6614 //return focus to the input element if a match was selected via a mouse click event 6615 // use timeout to avoid $rootScope:inprog error 6616 if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) { 6617 $timeout(function() { element[0].focus(); }, 0, false); 6618 } 6619 }; 6620 6621 //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27) 6622 element.on('keydown', function(evt) { 6623 //typeahead is open and an "interesting" key was pressed 6624 if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) { 6625 return; 6626 } 6627 6628 var shouldSelect = isSelectEvent(originalScope, {$event: evt}); 6629 6630 /** 6631 * if there's nothing selected (i.e. focusFirst) and enter or tab is hit 6632 * or 6633 * shift + tab is pressed to bring focus to the previous element 6634 * then clear the results 6635 */ 6636 if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) { 6637 resetMatches(); 6638 scope.$digest(); 6639 return; 6640 } 6641 6642 evt.preventDefault(); 6643 var target; 6644 switch (evt.which) { 6645 case 27: // escape 6646 evt.stopPropagation(); 6647 6648 resetMatches(); 6649 originalScope.$digest(); 6650 break; 6651 case 38: // up arrow 6652 scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; 6653 scope.$digest(); 6654 target = popUpEl.find('li')[scope.activeIdx]; 6655 target.parentNode.scrollTop = target.offsetTop; 6656 break; 6657 case 40: // down arrow 6658 scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; 6659 scope.$digest(); 6660 target = popUpEl.find('li')[scope.activeIdx]; 6661 target.parentNode.scrollTop = target.offsetTop; 6662 break; 6663 default: 6664 if (shouldSelect) { 6665 scope.$apply(function() { 6666 if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { 6667 $$debounce(function() { 6668 scope.select(scope.activeIdx, evt); 6669 }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); 6670 } else { 6671 scope.select(scope.activeIdx, evt); 6672 } 6673 }); 6674 } 6675 } 6676 }); 6677 6678 element.bind('focus', function (evt) { 6679 hasFocus = true; 6680 if (minLength === 0 && !modelCtrl.$viewValue) { 6681 $timeout(function() { 6682 getMatchesAsync(modelCtrl.$viewValue, evt); 6683 }, 0); 6684 } 6685 }); 6686 6687 element.bind('blur', function(evt) { 6688 if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) { 6689 selected = true; 6690 scope.$apply(function() { 6691 if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) { 6692 $$debounce(function() { 6693 scope.select(scope.activeIdx, evt); 6694 }, scope.debounceUpdate.blur); 6695 } else { 6696 scope.select(scope.activeIdx, evt); 6697 } 6698 }); 6699 } 6700 if (!isEditable && modelCtrl.$error.editable) { 6701 modelCtrl.$setViewValue(); 6702 // Reset validity as we are clearing 6703 modelCtrl.$setValidity('editable', true); 6704 modelCtrl.$setValidity('parse', true); 6705 element.val(''); 6706 } 6707 hasFocus = false; 6708 selected = false; 6709 }); 6710 6711 // Keep reference to click handler to unbind it. 6712 var dismissClickHandler = function(evt) { 6713 // Issue #3973 6714 // Firefox treats right click as a click on document 6715 if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) { 6716 resetMatches(); 6717 if (!$rootScope.$$phase) { 6718 originalScope.$digest(); 6719 } 6720 } 6721 }; 6722 6723 $document.on('click', dismissClickHandler); 6724 6725 originalScope.$on('$destroy', function() { 6726 $document.off('click', dismissClickHandler); 6727 if (appendToBody || appendTo) { 6728 $popup.remove(); 6729 } 6730 6731 if (appendToBody) { 6732 angular.element($window).off('resize', fireRecalculating); 6733 $document.find('body').off('scroll', fireRecalculating); 6734 } 6735 // Prevent jQuery cache memory leak 6736 popUpEl.remove(); 6737 6738 if (showHint) { 6739 inputsContainer.remove(); 6740 } 6741 }); 6742 6743 var $popup = $compile(popUpEl)(scope); 6744 6745 if (appendToBody) { 6746 $document.find('body').append($popup); 6747 } else if (appendTo) { 6748 angular.element(appendTo).eq(0).append($popup); 6749 } else { 6750 element.after($popup); 6751 } 6752 6753 this.init = function(_modelCtrl, _ngModelOptions) { 6754 modelCtrl = _modelCtrl; 6755 ngModelOptions = _ngModelOptions; 6756 6757 scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope); 6758 6759 //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM 6760 //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue 6761 modelCtrl.$parsers.unshift(function(inputValue) { 6762 hasFocus = true; 6763 6764 if (minLength === 0 || inputValue && inputValue.length >= minLength) { 6765 if (waitTime > 0) { 6766 cancelPreviousTimeout(); 6767 scheduleSearchWithTimeout(inputValue); 6768 } else { 6769 getMatchesAsync(inputValue); 6770 } 6771 } else { 6772 isLoadingSetter(originalScope, false); 6773 cancelPreviousTimeout(); 6774 resetMatches(); 6775 } 6776 6777 if (isEditable) { 6778 return inputValue; 6779 } 6780 6781 if (!inputValue) { 6782 // Reset in case user had typed something previously. 6783 modelCtrl.$setValidity('editable', true); 6784 return null; 6785 } 6786 6787 modelCtrl.$setValidity('editable', false); 6788 return undefined; 6789 }); 6790 6791 modelCtrl.$formatters.push(function(modelValue) { 6792 var candidateViewValue, emptyViewValue; 6793 var locals = {}; 6794 6795 // The validity may be set to false via $parsers (see above) if 6796 // the model is restricted to selected values. If the model 6797 // is set manually it is considered to be valid. 6798 if (!isEditable) { 6799 modelCtrl.$setValidity('editable', true); 6800 } 6801 6802 if (inputFormatter) { 6803 locals.$model = modelValue; 6804 return inputFormatter(originalScope, locals); 6805 } 6806 6807 //it might happen that we don't have enough info to properly render input value 6808 //we need to check for this situation and simply return model value if we can't apply custom formatting 6809 locals[parserResult.itemName] = modelValue; 6810 candidateViewValue = parserResult.viewMapper(originalScope, locals); 6811 locals[parserResult.itemName] = undefined; 6812 emptyViewValue = parserResult.viewMapper(originalScope, locals); 6813 6814 return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue; 6815 }); 6816 }; 6817 }]) 6818 6819 .directive('uibTypeahead', function() { 6820 return { 6821 controller: 'UibTypeaheadController', 6822 require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'], 6823 link: function(originalScope, element, attrs, ctrls) { 6824 ctrls[2].init(ctrls[0], ctrls[1]); 6825 } 6826 }; 6827 }) 6828 6829 .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) { 6830 return { 6831 scope: { 6832 matches: '=', 6833 query: '=', 6834 active: '=', 6835 position: '&', 6836 moveInProgress: '=', 6837 select: '&', 6838 assignIsOpen: '&', 6839 debounce: '&' 6840 }, 6841 replace: true, 6842 templateUrl: function(element, attrs) { 6843 return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html'; 6844 }, 6845 link: function(scope, element, attrs) { 6846 scope.templateUrl = attrs.templateUrl; 6847 6848 scope.isOpen = function() { 6849 var isDropdownOpen = scope.matches.length > 0; 6850 scope.assignIsOpen({ isOpen: isDropdownOpen }); 6851 return isDropdownOpen; 6852 }; 6853 6854 scope.isActive = function(matchIdx) { 6855 return scope.active === matchIdx; 6856 }; 6857 6858 scope.selectActive = function(matchIdx) { 6859 scope.active = matchIdx; 6860 }; 6861 6862 scope.selectMatch = function(activeIdx, evt) { 6863 var debounce = scope.debounce(); 6864 if (angular.isNumber(debounce) || angular.isObject(debounce)) { 6865 $$debounce(function() { 6866 scope.select({activeIdx: activeIdx, evt: evt}); 6867 }, angular.isNumber(debounce) ? debounce : debounce['default']); 6868 } else { 6869 scope.select({activeIdx: activeIdx, evt: evt}); 6870 } 6871 }; 6872 } 6873 }; 6874 }]) 6875 6876 .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) { 6877 return { 6878 scope: { 6879 index: '=', 6880 match: '=', 6881 query: '=' 6882 }, 6883 link: function(scope, element, attrs) { 6884 var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html'; 6885 $templateRequest(tplUrl).then(function(tplContent) { 6886 var tplEl = angular.element(tplContent.trim()); 6887 element.replaceWith(tplEl); 6888 $compile(tplEl)(scope); 6889 }); 6890 } 6891 }; 6892 }]) 6893 6894 .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) { 6895 var isSanitizePresent; 6896 isSanitizePresent = $injector.has('$sanitize'); 6897 6898 function escapeRegexp(queryToEscape) { 6899 // Regex: capture the whole query string and replace it with the string that will be used to match 6900 // the results, for example if the capture is "a" the result will be \a 6901 return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1'); 6902 } 6903 6904 function containsHtml(matchItem) { 6905 return /<.*>/g.test(matchItem); 6906 } 6907 6908 return function(matchItem, query) { 6909 if (!isSanitizePresent && containsHtml(matchItem)) { 6910 $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger 6911 } 6912 matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag 6913 if (!isSanitizePresent) { 6914 matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive 6915 } 6916 return matchItem; 6917 }; 6918 }]); 6919 6920angular.module("uib/template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) { 6921 $templateCache.put("uib/template/accordion/accordion-group.html", 6922 "<div class=\"panel\" ng-class=\"panelClass || 'panel-default'\">\n" + 6923 " <div role=\"tab\" id=\"{{::headingId}}\" aria-selected=\"{{isOpen}}\" class=\"panel-heading\" ng-keypress=\"toggleOpen($event)\">\n" + 6924 " <h4 class=\"panel-title\">\n" + 6925 " <a role=\"button\" data-toggle=\"collapse\" href aria-expanded=\"{{isOpen}}\" aria-controls=\"{{::panelId}}\" tabindex=\"0\" class=\"accordion-toggle\" ng-click=\"toggleOpen()\" uib-accordion-transclude=\"heading\"><span uib-accordion-header ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" + 6926 " </h4>\n" + 6927 " </div>\n" + 6928 " <div id=\"{{::panelId}}\" aria-labelledby=\"{{::headingId}}\" aria-hidden=\"{{!isOpen}}\" role=\"tabpanel\" class=\"panel-collapse collapse\" uib-collapse=\"!isOpen\">\n" + 6929 " <div class=\"panel-body\" ng-transclude></div>\n" + 6930 " </div>\n" + 6931 "</div>\n" + 6932 ""); 6933}]); 6934 6935angular.module("uib/template/accordion/accordion.html", []).run(["$templateCache", function($templateCache) { 6936 $templateCache.put("uib/template/accordion/accordion.html", 6937 "<div role=\"tablist\" class=\"panel-group\" ng-transclude></div>"); 6938}]); 6939 6940angular.module("uib/template/alert/alert.html", []).run(["$templateCache", function($templateCache) { 6941 $templateCache.put("uib/template/alert/alert.html", 6942 "<div class=\"alert\" ng-class=\"['alert-' + (type || 'warning'), closeable ? 'alert-dismissible' : null]\" role=\"alert\">\n" + 6943 " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close({$event: $event})\">\n" + 6944 " <span aria-hidden=\"true\">×</span>\n" +
6945 " <span class=\"sr-only\">Close</span>\n" + 6946 " </button>\n" + 6947 " <div ng-transclude></div>\n" + 6948 "</div>\n" + 6949 ""); 6950}]); 6951 6952angular.module("uib/template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) { 6953 $templateCache.put("uib/template/carousel/carousel.html", 6954 "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" + 6955 " <div class=\"carousel-inner\" ng-transclude></div>\n" + 6956 " <a role=\"button\" href class=\"left carousel-control\" ng-click=\"prev()\" ng-class=\"{ disabled: isPrevDisabled() }\" ng-show=\"slides.length > 1\">\n" + 6957 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-left\"></span>\n" + 6958 " <span class=\"sr-only\">previous</span>\n" + 6959 " </a>\n" + 6960 " <a role=\"button\" href class=\"right carousel-control\" ng-click=\"next()\" ng-class=\"{ disabled: isNextDisabled() }\" ng-show=\"slides.length > 1\">\n" + 6961 " <span aria-hidden=\"true\" class=\"glyphicon glyphicon-chevron-right\"></span>\n" + 6962 " <span class=\"sr-only\">next</span>\n" + 6963 " </a>\n" + 6964 " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" + 6965 " <li ng-repeat=\"slide in slides | orderBy:indexOfSlide track by $index\" ng-class=\"{ active: isActive(slide) }\" ng-click=\"select(slide)\">\n" + 6966 " <span class=\"sr-only\">slide {{ $index + 1 }} of {{ slides.length }}<span ng-if=\"isActive(slide)\">, currently active</span></span>\n" + 6967 " </li>\n" + 6968 " </ol>\n" + 6969 "</div>\n" + 6970 ""); 6971}]); 6972 6973angular.module("uib/template/carousel/slide.html", []).run(["$templateCache", function($templateCache) { 6974 $templateCache.put("uib/template/carousel/slide.html", 6975 "<div ng-class=\"{\n" + 6976 " 'active': active\n" + 6977 " }\" class=\"item text-center\" ng-transclude></div>\n" + 6978 ""); 6979}]); 6980 6981angular.module("uib/template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) { 6982 $templateCache.put("uib/template/datepicker/datepicker.html", 6983 "<div class=\"uib-datepicker\" ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" + 6984 " <uib-daypicker ng-switch-when=\"day\" tabindex=\"0\"></uib-daypicker>\n" + 6985 " <uib-monthpicker ng-switch-when=\"month\" tabindex=\"0\"></uib-monthpicker>\n" + 6986 " <uib-yearpicker ng-switch-when=\"year\" tabindex=\"0\"></uib-yearpicker>\n" + 6987 "</div>\n" + 6988 ""); 6989}]); 6990 6991angular.module("uib/template/datepicker/day.html", []).run(["$templateCache", function($templateCache) { 6992 $templateCache.put("uib/template/datepicker/day.html", 6993 "<table class=\"uib-daypicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 6994 " <thead>\n" + 6995 " <tr>\n" + 6996 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 6997 " <th colspan=\"{{::5 + showWeeks}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" + 6998 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 6999 " </tr>\n" + 7000 " <tr>\n" + 7001 " <th ng-if=\"showWeeks\" class=\"text-center\"></th>\n" + 7002 " <th ng-repeat=\"label in ::labels track by $index\" class=\"text-center\"><small aria-label=\"{{::label.full}}\">{{::label.abbr}}</small></th>\n" + 7003 " </tr>\n" + 7004 " </thead>\n" + 7005 " <tbody>\n" + 7006 " <tr class=\"uib-weeks\" ng-repeat=\"row in rows track by $index\">\n" + 7007 " <td ng-if=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" + 7008 " <td ng-repeat=\"dt in row\" class=\"uib-day text-center\" role=\"gridcell\"\n" + 7009 " id=\"{{::dt.uid}}\"\n" + 7010 " ng-class=\"::dt.customClass\">\n" + 7011 " <button type=\"button\" class=\"btn btn-default btn-sm\"\n" + 7012 " uib-is-class=\"\n" + 7013 " 'btn-info' for selectedDt,\n" + 7014 " 'active' for activeDt\n" + 7015 " on dt\"\n" + 7016 " ng-click=\"select(dt.date)\"\n" + 7017 " ng-disabled=\"::dt.disabled\"\n" + 7018 " tabindex=\"-1\"><span ng-class=\"::{'text-muted': dt.secondary, 'text-info': dt.current}\">{{::dt.label}}</span></button>\n" + 7019 " </td>\n" + 7020 " </tr>\n" + 7021 " </tbody>\n" + 7022 "</table>\n" + 7023 ""); 7024}]); 7025 7026angular.module("uib/template/datepicker/month.html", []).run(["$templateCache", function($templateCache) { 7027 $templateCache.put("uib/template/datepicker/month.html", 7028 "<table class=\"uib-monthpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 7029 " <thead>\n" + 7030 " <tr>\n" + 7031 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 7032 " <th><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === max
7032Mode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" + 7033 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 7034 " </tr>\n" + 7035 " </thead>\n" + 7036 " <tbody>\n" + 7037 " <tr class=\"uib-months\" ng-repeat=\"row in rows track by $index\">\n" + 7038 " <td ng-repeat=\"dt in row\" class=\"uib-month text-center\" role=\"gridcell\"\n" + 7039 " id=\"{{::dt.uid}}\"\n" + 7040 " ng-class=\"::dt.customClass\">\n" + 7041 " <button type=\"button\" class=\"btn btn-default\"\n" + 7042 " uib-is-class=\"\n" + 7043 " 'btn-info' for selectedDt,\n" + 7044 " 'active' for activeDt\n" + 7045 " on dt\"\n" + 7046 " ng-click=\"select(dt.date)\"\n" + 7047 " ng-disabled=\"::dt.disabled\"\n" + 7048 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" + 7049 " </td>\n" + 7050 " </tr>\n" + 7051 " </tbody>\n" + 7052 "</table>\n" + 7053 ""); 7054}]); 7055 7056angular.module("uib/template/datepicker/year.html", []).run(["$templateCache", function($templateCache) { 7057 $templateCache.put("uib/template/datepicker/year.html", 7058 "<table class=\"uib-yearpicker\" role=\"grid\" aria-labelledby=\"{{::uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" + 7059 " <thead>\n" + 7060 " <tr>\n" + 7061 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left uib-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" + 7062 " <th colspan=\"{{::columns - 2}}\"><button id=\"{{::uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm uib-title\" ng-click=\"toggleMode()\" ng-disabled=\"datepickerMode === maxMode\" tabindex=\"-1\"><strong>{{title}}</strong></button></th>\n" + 7063 " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right uib-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" + 7064 " </tr>\n" + 7065 " </thead>\n" + 7066 " <tbody>\n" + 7067 " <tr class=\"uib-years\" ng-repeat=\"row in rows track by $index\">\n" + 7068 " <td ng-repeat=\"dt in row\" class=\"uib-year text-center\" role=\"gridcell\"\n" + 7069 " id=\"{{::dt.uid}}\"\n" + 7070 " ng-class=\"::dt.customClass\">\n" + 7071 " <button type=\"button\" class=\"btn btn-default\"\n" + 7072 " uib-is-class=\"\n" + 7073 " 'btn-info' for selectedDt,\n" + 7074 " 'active' for activeDt\n" + 7075 " on dt\"\n" + 7076 " ng-click=\"select(dt.date)\"\n" + 7077 " ng-disabled=\"::dt.disabled\"\n" + 7078 " tabindex=\"-1\"><span ng-class=\"::{'text-info': dt.current}\">{{::dt.label}}</span></button>\n" + 7079 " </td>\n" + 7080 " </tr>\n" + 7081 " </tbody>\n" + 7082 "</table>\n" + 7083 ""); 7084}]); 7085 7086angular.module("uib/template/datepickerPopup/popup.html", []).run(["$templateCache", function($templateCache) { 7087 $templateCache.put("uib/template/datepickerPopup/popup.html", 7088 "<div>\n" + 7089 " <ul class=\"uib-datepicker-popup dropdown-menu uib-position-measure\" dropdown-nested ng-if=\"isOpen\" ng-keydown=\"keydown($event)\" ng-click=\"$event.stopPropagation()\">\n" + 7090 " <li ng-transclude></li>\n" + 7091 " <li ng-if=\"showButtonBar\" class=\"uib-button-bar\">\n" + 7092 " <span class=\"btn-group pull-left\">\n" + 7093 " <button type=\"button\" class=\"btn btn-sm btn-info uib-datepicker-current\" ng-click=\"select('today', $event)\" ng-disabled=\"isDisabled('today')\">{{ getText('current') }}</button>\n" + 7094 " <button type=\"button\" class=\"btn btn-sm btn-danger uib-clear\" ng-click=\"select(null, $event)\">{{ getText('clear') }}</button>\n" + 7095 " </span>\n" + 7096 " <button type=\"button\" class=\"btn btn-sm btn-success pull-right uib-close\" ng-click=\"close($event)\">{{ getText('close') }}</button>\n" + 7097 " </li>\n" + 7098 " </ul>\n" + 7099 "</div>\n" + 7100 ""); 7101}]); 7102 7103angular.module("uib/template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) { 7104 $templateCache.put("uib/template/modal/backdrop.html", 7105 "<div class=\"modal-backdrop\"\n" + 7106 " uib-modal-animation-class=\"fade\"\n" + 7107 " modal-in-class=\"in\"\n" +
7108 " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" + 7109 "></div>\n" + 7110 ""); 7111}]); 7112 7113angular.module("uib/template/modal/window.html", []).run(["$templateCache", function($templateCache) { 7114 $templateCache.put("uib/template/modal/window.html", 7115 "<div modal-render=\"{{$isRendered}}\" tabindex=\"-1\" role=\"dialog\" class=\"modal\"\n" + 7116 " uib-modal-animation-class=\"fade\"\n" + 7117 " modal-in-class=\"in\"\n" + 7118 " ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\">\n" + 7119 " <div class=\"modal-dialog {{size ? 'modal-' + size : ''}}\"><div class=\"modal-content\" uib-modal-transclude></div></div>\n" + 7120 "</div>\n" + 7121 ""); 7122}]); 7123 7124angular.module("uib/template/pager/pager.html", []).run(["$templateCache", function($templateCache) { 7125 $templateCache.put("uib/template/pager/pager.html", 7126 "<ul class=\"pager\">\n" + 7127 " <li ng-class=\"{disabled: noPrevious()||ngDisabled, previous: align}\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" + 7128 " <li ng-class=\"{disabled: noNext()||ngDisabled, next: align}\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" + 7129 "</ul>\n" + 7130 ""); 7131}]); 7132 7133angular.module("uib/template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) { 7134 $templateCache.put("uib/template/pagination/pagination.html", 7135 "<ul class=\"pagination\">\n" + 7136 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-first\"><a href ng-click=\"selectPage(1, $event)\">{{::getText('first')}}</a></li>\n" + 7137 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noPrevious()||ngDisabled}\" class=\"pagination-prev\"><a href ng-click=\"selectPage(page - 1, $event)\">{{::getText('previous')}}</a></li>\n" + 7138 " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active,disabled: ngDisabled&&!page.active}\" class=\"pagination-page\"><a href ng-click=\"selectPage(page.number, $event)\">{{page.text}}</a></li>\n" + 7139 " <li ng-if=\"::directionLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-next\"><a href ng-click=\"selectPage(page + 1, $event)\">{{::getText('next')}}</a></li>\n" + 7140 " <li ng-if=\"::boundaryLinks\" ng-class=\"{disabled: noNext()||ngDisabled}\" class=\"pagination-last\"><a href ng-click=\"selectPage(totalPages, $event)\">{{::getText('last')}}</a></li>\n" + 7141 "</ul>\n" + 7142 ""); 7143}]); 7144 7145angular.module("uib/template/tooltip/tooltip-html-popup.html", []).run(["$templateCache", function($templateCache) { 7146 $templateCache.put("uib/template/tooltip/tooltip-html-popup.html", 7147 "<div class=\"tooltip\"\n" + 7148 " tooltip-animation-class=\"fade\"\n" + 7149 " uib-tooltip-classes\n" + 7150 " ng-class=\"{ in: isOpen() }\">\n" + 7151 " <div class=\"tooltip-arrow\"></div>\n" + 7152 " <div class=\"tooltip-inner\" ng-bind-html=\"contentExp()\"></div>\n" + 7153 "</div>\n" + 7154 ""); 7155}]); 7156 7157angular.module("uib/template/tooltip/tooltip-popup.html", []).run(["$templateCache", function($templateCache) { 7158 $templateCache.put("uib/template/tooltip/tooltip-popup.html", 7159 "<div class=\"tooltip\"\n" + 7160 " tooltip-animation-class=\"fade\"\n" + 7161 " uib-tooltip-classes\n" + 7162 " ng-class=\"{ in: isOpen() }\">\n" + 7163 " <div class=\"tooltip-arrow\"></div>\n" + 7164 " <div class=\"tooltip-inner\" ng-bind=\"content\"></div>\n" + 7165 "</div>\n" + 7166 ""); 7167}]); 7168 7169angular.module("uib/template/tooltip/tooltip-template-popup.html", []).run(["$templateCache", function($templateCache) { 7170 $templateCache.put("uib/template/tooltip/tooltip-template-popup.html", 7171 "<div class=\"tooltip\"\n" + 7172 " tooltip-animation-class=\"fade\"\n" + 7173 " uib-tooltip-classes\n" + 7174 " ng-class=\"{ in: isOpen() }\">\n" + 7175 " <div class=\"tooltip-arrow\"></div>\n" + 7176 " <div class=\"tooltip-inner\"\n" + 7177 " uib-tooltip-template-transclude=\"contentExp()\"\n" + 7178 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" + 7179 "</div>\n" + 7180 ""); 7181}]); 7182 7183angular.module("uib/template/popover/popover-html.html", []).run(["$templateCache", function($templateCache) { 7184 $templateCache.put("uib/template/popover/popover-html.html", 7185 "<div class=\"popover\"\n" + 7186 " tooltip-animation-class=\"fade\"\n" +
7187 " uib-tooltip-classes\n" + 7188 " ng-class=\"{ in: isOpen() }\">\n" + 7189 " <div class=\"arrow\"></div>\n" + 7190 "\n" + 7191 " <div class=\"popover-inner\">\n" + 7192 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" + 7193 " <div class=\"popover-content\" ng-bind-html=\"contentExp()\"></div>\n" + 7194 " </div>\n" + 7195 "</div>\n" + 7196 ""); 7197}]); 7198 7199angular.module("uib/template/popover/popover-template.html", []).run(["$templateCache", function($templateCache) { 7200 $templateCache.put("uib/template/popover/popover-template.html", 7201 "<div class=\"popover\"\n" + 7202 " tooltip-animation-class=\"fade\"\n" + 7203 " uib-tooltip-classes\n" + 7204 " ng-class=\"{ in: isOpen() }\">\n" + 7205 " <div class=\"arrow\"></div>\n" + 7206 "\n" + 7207 " <div class=\"popover-inner\">\n" + 7208 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" + 7209 " <div class=\"popover-content\"\n" + 7210 " uib-tooltip-template-transclude=\"contentExp()\"\n" + 7211 " tooltip-template-transclude-scope=\"originScope()\"></div>\n" + 7212 " </div>\n" + 7213 "</div>\n" + 7214 ""); 7215}]); 7216 7217angular.module("uib/template/popover/popover.html", []).run(["$templateCache", function($templateCache) { 7218 $templateCache.put("uib/template/popover/popover.html", 7219 "<div class=\"popover\"\n" + 7220 " tooltip-animation-class=\"fade\"\n" + 7221 " uib-tooltip-classes\n" + 7222 " ng-class=\"{ in: isOpen() }\">\n" + 7223 " <div class=\"arrow\"></div>\n" + 7224 "\n" + 7225 " <div class=\"popover-inner\">\n" + 7226 " <h3 class=\"popover-title\" ng-bind=\"uibTitle\" ng-if=\"uibTitle\"></h3>\n" + 7227 " <div class=\"popover-content\" ng-bind=\"content\"></div>\n" + 7228 " </div>\n" + 7229 "</div>\n" + 7230 ""); 7231}]); 7232 7233angular.module("uib/template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) { 7234 $templateCache.put("uib/template/progressbar/bar.html", 7235 "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" + 7236 ""); 7237}]); 7238 7239angular.module("uib/template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) { 7240 $templateCache.put("uib/template/progressbar/progress.html", 7241 "<div class=\"progress\" ng-transclude aria-labelledby=\"{{::title}}\"></div>"); 7242}]); 7243 7244angular.module("uib/template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) { 7245 $templateCache.put("uib/template/progressbar/progressbar.html", 7246 "<div class=\"progress\">\n" + 7247 " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: (percent < 100 ? percent : 100) + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" aria-labelledby=\"{{::title}}\" ng-transclude></div>\n" + 7248 "</div>\n" + 7249 ""); 7250}]); 7251 7252angular.module("uib/template/rating/rating.html", []).run(["$templateCache", function($templateCache) { 7253 $templateCache.put("uib/template/rating/rating.html", 7254 "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\" aria-valuetext=\"{{title}}\">\n" + 7255 " <span ng-repeat-start=\"r in range track by $index\" class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" + 7256 " <i ng-repeat-end ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\" ng-attr-title=\"{{r.title}}\"></i>\n" + 7257 "</span>\n" + 7258 ""); 7259}]); 7260 7261angular.module("uib/template/tabs/tab.html", []).run(["$templateCache", function($templateCache) { 7262 $templateCache.put("uib/template/tabs/tab.html", 7263 "<li ng-class=\"[{active: active, disabled: disabled}, classes]\" class=\"uib-tab nav-item\">\n" + 7264 " <a href ng-click=\"select($event)\" class=\"nav-link\" uib-tab-heading-transclude>{{heading}}</a>\n" + 7265 "</li>\n" + 7266 ""); 7267}]); 7268 7269angular.module("uib/template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) { 7270 $templateCache.put("uib/template/tabs/tabset.html", 7271 "<div>\n" + 7272 " <ul class=\"nav nav-{{tabset.type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" + 7273 " <div class=\"tab-content\">\n" + 7274 " <div class=\"tab-pane\"\n" + 7275 " ng-repeat=\"tab in tabset.tabs\"\n" + 7276 " ng-class=\"{active: tabset.active === tab.index}\"\n" + 7277 " uib-tab-content-transclude=\"tab\">\n" + 7278 " </div>\n" + 7279 " </div>\n" + 7280 "</div>\n" + 7281 ""); 7282}]); 7283 7284angular.module("uib/template/timepicker/timepicker.html", []).run(["$templateCache", function($templateCache) { 7285 $templateCache.put("uib/template/timepicker/timepicker.html", 7286 "<table class=\"uib-timepicker\">\n" + 7287 " <tbody>\n" + 7288 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" + 7289 " <td class=\"uib-increment hours\"><a ng-click=\"incrementHours()\" ng-class=\"{disabled: noIncrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 7290 " <td> </td>\n" + 7291 " <td class=\"uib-increment minutes\"><a ng-click=\"incrementMinutes()\" ng-class=\"{disabled: noIncrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 7292 " <td ng-show=\"showSeconds\"> </td>\n" + 7293 " <td ng-show=\"showSeconds\" class=\"uib-increment seconds\"><a ng-click=\"incrementSeconds()\" ng-class=\"{disabled: noIncrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noIncrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-up\"></span></a></td>\n" + 7294 " <td ng-show=\"showMeridian\"></td>\n" + 7295 " </tr>\n" + 7296 " <tr>\n" + 7297 " <td class=\"form-group uib-time hours\" ng-class=\"{'has-error': invalidHours}\">\n" + 7298 " <input type=\"text\" placeholder=\"HH\" ng-model=\"hours\" ng-change=\"updateHours()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementHours()\" ng-blur=\"blur()\">\n" + 7299 " </td>\n" + 7300 " <td class=\"uib-separator\">:</td>\n" + 7301 " <td class=\"form-group uib-time minutes\" ng-class=\"{'has-error': invalidMinutes}\">\n" + 7302 " <input type=\"text\" placeholder=\"MM\" ng-model=\"minutes\" ng-change=\"updateMinutes()\" class=\"form-control text-center\" ng-readonly=\"::readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementMinutes()\" ng-blur=\"blur()\">\n" + 7303 " </td>\n" + 7304 " <td ng-show=\"showSeconds\" class=\"uib-separator\">:</td>\n" + 7305 " <td class=\"form-group uib-time seconds\" ng-class=\"{'has-error': invalidSeconds}\" ng-show=\"showSeconds\">\n" + 7306 " <input type=\"text\" placeholder=\"SS\" ng-model=\"seconds\" ng-change=\"updateSeconds()\" class=\"form-control text-center\" ng-readonly=\"readonlyInput\" maxlength=\"2\" tabindex=\"{{::tabindex}}\" ng-disabled=\"noIncrementSeconds()\" ng-blur=\"blur()\">\n" + 7307 " </td>\n" + 7308 " <td ng-show=\"showMeridian\" class=\"uib-time am-pm\"><button type=\"button\" ng-class=\"{disabled: noToggleMeridian()}\" class=\"btn btn-default text-center\" ng-click=\"toggleMeridian()\" ng-disabled=\"noToggleMeridian()\" tabindex=\"{{::tabindex}}\">{{meridian}}</button></td>\n" + 7309 " </tr>\n" + 7310 " <tr class=\"text-center\" ng-show=\"::showSpinners\">\n" + 7311 " <td class=\"uib-decrement hours\"><a ng-click=\"decrementHours()\" ng-class=\"{disabled: noDecrementHours()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementHours()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 7312 " <td> </td>\n" + 7313 " <td class=\"uib-decrement minutes\"><a ng-click=\"decrementMinutes()\" ng-class=\"{disabled: noDecrementMinutes()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementMinutes()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 7314 " <td ng-show=\"showSeconds\"> </td>\n" + 7315 " <td ng-show=\"showSeconds\" class=\"uib-decrement seconds\"><a ng-click=\"decrementSeconds()\" ng-class=\"{disabled: noDecrementSeconds()}\" class=\"btn btn-link\" ng-disabled=\"noDecrementSeconds()\" tabindex=\"{{::tabindex}}\"><span class=\"glyphicon glyphicon-chevron-down\"></span></a></td>\n" + 7316 " <td ng-show=\"showMeridian\"></td>\n" + 7317 " </tr>\n" + 7318 " </tbody>\n" + 7319 "</table>\n" + 7320 ""); 7321}]); 7322 7323angular.module("uib/template/typeahead/typeahead-match.html", []).run(["$templateCache", function($templateCache) { 7324 $templateCache.put("uib/template/typeahead/typeahead-match.html", 7325 "<a href\n" + 7326 " tabindex=\"-1\"\n" + 7327 " ng-bind-html=\"match.label | uibTypeaheadHighlight:query\"\n" + 7328 " ng-attr-title=\"{{match.label}}\"></a>\n" + 7329 ""); 7330}]); 7331 7332angular.module("uib/template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) { 7333 $templateCache.put("uib/template/typeahead/typeahead-popup.html", 7334 "<ul class=\"dropdown-menu\" ng-show=\"isOpen() && !moveInProgress\" ng-style=\"{top: position().top+'px', left: position().left+'px'}\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" + 7335 " <li
vendor: 5,803 bytes, lines 7335-7347
7335 ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index, $event)\" role=\"option\" id=\"{{::match.id}}\">\n" + 7336 " <div uib-typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" + 7337 " </li>\n" + 7338 "</ul>\n" + 7339 ""); 7340}]); 7341angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); angular.$$uibCarouselCss = true; }); 7342angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; }); 7343angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; }); 7344angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; }); 7345angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; }); 7346angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; }); 7347angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });
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.