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