angular-ui-bootstrap-rails 0.12.1 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,154 +2,46 @@
2
2
  * angular-ui-bootstrap
3
3
  * http://angular-ui.github.io/bootstrap/
4
4
 
5
- * Version: 0.12.1 - 2015-02-20
5
+ * Version: 0.13.0 - 2015-05-02
6
6
  * License: MIT
7
7
  */
8
- angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
9
- angular.module('ui.bootstrap.transition', [])
10
-
11
- /**
12
- * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
13
- * @param {DOMElement} element The DOMElement that will be animated.
14
- * @param {string|object|function} trigger The thing that will cause the transition to start:
15
- * - As a string, it represents the css class to be added to the element.
16
- * - As an object, it represents a hash of style attributes to be applied to the element.
17
- * - As a function, it represents a function to be called that will cause the transition to occur.
18
- * @return {Promise} A promise that is resolved when the transition finishes.
19
- */
20
- .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
8
+ angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.transition","ui.bootstrap.typeahead"]);
9
+ angular.module('ui.bootstrap.collapse', [])
21
10
 
22
- var $transition = function(element, trigger, options) {
23
- options = options || {};
24
- var deferred = $q.defer();
25
- var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName'];
26
-
27
- var transitionEndHandler = function(event) {
28
- $rootScope.$apply(function() {
29
- element.unbind(endEventName, transitionEndHandler);
30
- deferred.resolve(element);
31
- });
32
- };
33
-
34
- if (endEventName) {
35
- element.bind(endEventName, transitionEndHandler);
36
- }
37
-
38
- // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
39
- $timeout(function() {
40
- if ( angular.isString(trigger) ) {
41
- element.addClass(trigger);
42
- } else if ( angular.isFunction(trigger) ) {
43
- trigger(element);
44
- } else if ( angular.isObject(trigger) ) {
45
- element.css(trigger);
46
- }
47
- //If browser does not support transitions, instantly resolve
48
- if ( !endEventName ) {
49
- deferred.resolve(element);
50
- }
51
- });
52
-
53
- // Add our custom cancel function to the promise that is returned
54
- // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
55
- // i.e. it will therefore never raise a transitionEnd event for that transition
56
- deferred.promise.cancel = function() {
57
- if ( endEventName ) {
58
- element.unbind(endEventName, transitionEndHandler);
59
- }
60
- deferred.reject('Transition cancelled');
61
- };
62
-
63
- return deferred.promise;
64
- };
65
-
66
- // Work out the name of the transitionEnd event
67
- var transElement = document.createElement('trans');
68
- var transitionEndEventNames = {
69
- 'WebkitTransition': 'webkitTransitionEnd',
70
- 'MozTransition': 'transitionend',
71
- 'OTransition': 'oTransitionEnd',
72
- 'transition': 'transitionend'
73
- };
74
- var animationEndEventNames = {
75
- 'WebkitTransition': 'webkitAnimationEnd',
76
- 'MozTransition': 'animationend',
77
- 'OTransition': 'oAnimationEnd',
78
- 'transition': 'animationend'
79
- };
80
- function findEndEventName(endEventNames) {
81
- for (var name in endEventNames){
82
- if (transElement.style[name] !== undefined) {
83
- return endEventNames[name];
84
- }
85
- }
86
- }
87
- $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
88
- $transition.animationEndEventName = findEndEventName(animationEndEventNames);
89
- return $transition;
90
- }]);
91
-
92
- angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
93
-
94
- .directive('collapse', ['$transition', function ($transition) {
11
+ .directive('collapse', ['$animate', function ($animate) {
95
12
 
96
13
  return {
97
14
  link: function (scope, element, attrs) {
98
-
99
- var initialAnimSkip = true;
100
- var currentTransition;
101
-
102
- function doTransition(change) {
103
- var newTransition = $transition(element, change);
104
- if (currentTransition) {
105
- currentTransition.cancel();
106
- }
107
- currentTransition = newTransition;
108
- newTransition.then(newTransitionDone, newTransitionDone);
109
- return newTransition;
110
-
111
- function newTransitionDone() {
112
- // Make sure it's this transition, otherwise, leave it alone.
113
- if (currentTransition === newTransition) {
114
- currentTransition = undefined;
115
- }
116
- }
117
- }
118
-
119
15
  function expand() {
120
- if (initialAnimSkip) {
121
- initialAnimSkip = false;
122
- expandDone();
123
- } else {
124
- element.removeClass('collapse').addClass('collapsing');
125
- doTransition({ height: element[0].scrollHeight + 'px' }).then(expandDone);
126
- }
16
+ element.removeClass('collapse').addClass('collapsing');
17
+ $animate.addClass(element, 'in', {
18
+ to: { height: element[0].scrollHeight + 'px' }
19
+ }).then(expandDone);
127
20
  }
128
21
 
129
22
  function expandDone() {
130
23
  element.removeClass('collapsing');
131
- element.addClass('collapse in');
132
24
  element.css({height: 'auto'});
133
25
  }
134
26
 
135
27
  function collapse() {
136
- if (initialAnimSkip) {
137
- initialAnimSkip = false;
138
- collapseDone();
139
- element.css({height: 0});
140
- } else {
141
- // CSS transitions don't work with height: auto, so we have to manually change the height to a specific value
142
- element.css({ height: element[0].scrollHeight + 'px' });
143
- //trigger reflow so a browser realizes that height was updated from auto to a specific value
144
- var x = element[0].offsetWidth;
145
-
146
- element.removeClass('collapse in').addClass('collapsing');
147
-
148
- doTransition({ height: 0 }).then(collapseDone);
149
- }
28
+ element
29
+ // IMPORTANT: The height must be set before adding "collapsing" class.
30
+ // Otherwise, the browser attempts to animate from height 0 (in
31
+ // collapsing class) to the given height here.
32
+ .css({height: element[0].scrollHeight + 'px'})
33
+ // initially all panel collapse have the collapse class, this removal
34
+ // prevents the animation from jumping to collapsed state
35
+ .removeClass('collapse')
36
+ .addClass('collapsing');
37
+
38
+ $animate.removeClass(element, 'in', {
39
+ to: {height: '0'}
40
+ }).then(collapseDone);
150
41
  }
151
42
 
152
43
  function collapseDone() {
44
+ element.css({height: '0'}); // Required so that collapse works when animation is disabled
153
45
  element.removeClass('collapsing');
154
46
  element.addClass('collapse');
155
47
  }
@@ -271,7 +163,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
271
163
  // Pass the heading to the accordion-group controller
272
164
  // so that it can be transcluded into the right place in the template
273
165
  // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
274
- accordionGroupCtrl.setHeading(transclude(scope, function() {}));
166
+ accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
275
167
  }
276
168
  };
277
169
  })
@@ -294,7 +186,9 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
294
186
  });
295
187
  }
296
188
  };
297
- });
189
+ })
190
+
191
+ ;
298
192
 
299
193
  angular.module('ui.bootstrap.alert', [])
300
194
 
@@ -421,8 +315,8 @@ angular.module('ui.bootstrap.buttons', [])
421
315
  * AngularJS version of an image carousel.
422
316
  *
423
317
  */
424
- angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
425
- .controller('CarouselController', ['$scope', '$timeout', '$interval', '$transition', function ($scope, $timeout, $interval, $transition) {
318
+ angular.module('ui.bootstrap.carousel', [])
319
+ .controller('CarouselController', ['$scope', '$interval', '$animate', function ($scope, $interval, $animate) {
426
320
  var self = this,
427
321
  slides = self.slides = $scope.slides = [],
428
322
  currentIndex = -1,
@@ -432,82 +326,76 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
432
326
  var destroyed = false;
433
327
  /* direction: "prev" or "next" */
434
328
  self.select = $scope.select = function(nextSlide, direction) {
435
- var nextIndex = slides.indexOf(nextSlide);
329
+ var nextIndex = self.indexOfSlide(nextSlide);
436
330
  //Decide direction if it's not given
437
331
  if (direction === undefined) {
438
- direction = nextIndex > currentIndex ? 'next' : 'prev';
332
+ direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
439
333
  }
440
334
  if (nextSlide && nextSlide !== self.currentSlide) {
441
- if ($scope.$currentTransition) {
442
- $scope.$currentTransition.cancel();
443
- //Timeout so ng-class in template has time to fix classes for finished slide
444
- $timeout(goNext);
445
- } else {
446
- goNext();
447
- }
335
+ goNext();
448
336
  }
449
337
  function goNext() {
450
338
  // Scope has been destroyed, stop here.
451
339
  if (destroyed) { return; }
452
- //If we have a slide to transition from and we have a transition type and we're allowed, go
453
- if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
454
- //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
455
- nextSlide.$element.addClass(direction);
456
- var reflow = nextSlide.$element[0].offsetWidth; //force reflow
457
-
458
- //Set all other slides to stop doing their stuff for the new transition
459
- angular.forEach(slides, function(slide) {
460
- angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
340
+
341
+ angular.extend(nextSlide, {direction: direction, active: true});
342
+ angular.extend(self.currentSlide || {}, {direction: direction, active: false});
343
+ if ($animate.enabled() && !$scope.noTransition && nextSlide.$element) {
344
+ $scope.$currentTransition = true;
345
+ nextSlide.$element.one('$animate:close', function closeFn() {
346
+ $scope.$currentTransition = null;
461
347
  });
462
- angular.extend(nextSlide, {direction: direction, active: true, entering: true});
463
- angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
464
-
465
- $scope.$currentTransition = $transition(nextSlide.$element, {});
466
- //We have to create new pointers inside a closure since next & current will change
467
- (function(next,current) {
468
- $scope.$currentTransition.then(
469
- function(){ transitionDone(next, current); },
470
- function(){ transitionDone(next, current); }
471
- );
472
- }(nextSlide, self.currentSlide));
473
- } else {
474
- transitionDone(nextSlide, self.currentSlide);
475
348
  }
349
+
476
350
  self.currentSlide = nextSlide;
477
351
  currentIndex = nextIndex;
478
352
  //every time you change slides, reset the timer
479
353
  restartTimer();
480
354
  }
481
- function transitionDone(next, current) {
482
- angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
483
- angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
484
- $scope.$currentTransition = null;
485
- }
486
355
  };
487
356
  $scope.$on('$destroy', function () {
488
357
  destroyed = true;
489
358
  });
490
359
 
360
+ function getSlideByIndex(index) {
361
+ if (angular.isUndefined(slides[index].index)) {
362
+ return slides[index];
363
+ }
364
+ var i, len = slides.length;
365
+ for (i = 0; i < slides.length; ++i) {
366
+ if (slides[i].index == index) {
367
+ return slides[i];
368
+ }
369
+ }
370
+ }
371
+
372
+ self.getCurrentIndex = function() {
373
+ if (self.currentSlide && angular.isDefined(self.currentSlide.index)) {
374
+ return +self.currentSlide.index;
375
+ }
376
+ return currentIndex;
377
+ };
378
+
491
379
  /* Allow outside people to call indexOf on slides array */
492
380
  self.indexOfSlide = function(slide) {
493
- return slides.indexOf(slide);
381
+ return angular.isDefined(slide.index) ? +slide.index : slides.indexOf(slide);
494
382
  };
495
383
 
496
384
  $scope.next = function() {
497
- var newIndex = (currentIndex + 1) % slides.length;
385
+ var newIndex = (self.getCurrentIndex() + 1) % slides.length;
498
386
 
499
387
  //Prevent this user-triggered transition from occurring if there is already one in progress
500
388
  if (!$scope.$currentTransition) {
501
- return self.select(slides[newIndex], 'next');
389
+ return self.select(getSlideByIndex(newIndex), 'next');
502
390
  }
503
391
  };
504
392
 
505
393
  $scope.prev = function() {
506
- var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
394
+ var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
507
395
 
508
396
  //Prevent this user-triggered transition from occurring if there is already one in progress
509
397
  if (!$scope.$currentTransition) {
510
- return self.select(slides[newIndex], 'prev');
398
+ return self.select(getSlideByIndex(newIndex), 'prev');
511
399
  }
512
400
  };
513
401
 
@@ -570,6 +458,11 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
570
458
  };
571
459
 
572
460
  self.removeSlide = function(slide) {
461
+ if (angular.isDefined(slide.index)) {
462
+ slides.sort(function(a, b) {
463
+ return +a.index > +b.index;
464
+ });
465
+ }
573
466
  //get the index of the slide inside the carousel
574
467
  var index = slides.indexOf(slide);
575
468
  slides.splice(index, 1);
@@ -649,13 +542,14 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
649
542
  * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
650
543
  *
651
544
  * @param {boolean=} active Model binding, whether or not this slide is currently active.
545
+ * @param {number=} index The index of the slide. The slides will be sorted by this parameter.
652
546
  *
653
547
  * @example
654
548
  <example module="ui.bootstrap">
655
549
  <file name="index.html">
656
550
  <div ng-controller="CarouselDemoCtrl">
657
551
  <carousel>
658
- <slide ng-repeat="slide in slides" active="slide.active">
552
+ <slide ng-repeat="slide in slides" active="slide.active" index="$index">
659
553
  <img ng-src="{{slide.image}}" style="margin:auto;">
660
554
  <div class="carousel-caption">
661
555
  <h4>Slide {{$index}}</h4>
@@ -689,7 +583,8 @@ function CarouselDemoCtrl($scope) {
689
583
  replace: true,
690
584
  templateUrl: 'template/carousel/slide.html',
691
585
  scope: {
692
- active: '=?'
586
+ active: '=?',
587
+ index: '=?'
693
588
  },
694
589
  link: function (scope, element, attrs, carouselCtrl) {
695
590
  carouselCtrl.addSlide(scope, element);
@@ -705,11 +600,64 @@ function CarouselDemoCtrl($scope) {
705
600
  });
706
601
  }
707
602
  };
708
- });
603
+ })
604
+
605
+ .animation('.item', [
606
+ '$animate',
607
+ function ($animate) {
608
+ return {
609
+ beforeAddClass: function (element, className, done) {
610
+ // Due to transclusion, noTransition property is on parent's scope
611
+ if (className == 'active' && element.parent() &&
612
+ !element.parent().scope().noTransition) {
613
+ var stopped = false;
614
+ var direction = element.isolateScope().direction;
615
+ var directionClass = direction == 'next' ? 'left' : 'right';
616
+ element.addClass(direction);
617
+ $animate.addClass(element, directionClass).then(function () {
618
+ if (!stopped) {
619
+ element.removeClass(directionClass + ' ' + direction);
620
+ }
621
+ done();
622
+ });
623
+
624
+ return function () {
625
+ stopped = true;
626
+ };
627
+ }
628
+ done();
629
+ },
630
+ beforeRemoveClass: function (element, className, done) {
631
+ // Due to transclusion, noTransition property is on parent's scope
632
+ if (className == 'active' && element.parent() &&
633
+ !element.parent().scope().noTransition) {
634
+ var stopped = false;
635
+ var direction = element.isolateScope().direction;
636
+ var directionClass = direction == 'next' ? 'left' : 'right';
637
+ $animate.addClass(element, directionClass).then(function () {
638
+ if (!stopped) {
639
+ element.removeClass(directionClass);
640
+ }
641
+ done();
642
+ });
643
+ return function () {
644
+ stopped = true;
645
+ };
646
+ }
647
+ done();
648
+ }
649
+ };
650
+
651
+ }])
652
+
653
+
654
+ ;
709
655
 
710
656
  angular.module('ui.bootstrap.dateparser', [])
711
657
 
712
658
  .service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) {
659
+ // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
660
+ var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
713
661
 
714
662
  this.parsers = {};
715
663
 
@@ -755,6 +703,34 @@ angular.module('ui.bootstrap.dateparser', [])
755
703
  },
756
704
  'EEE': {
757
705
  regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
706
+ },
707
+ 'HH': {
708
+ regex: '(?:0|1)[0-9]|2[0-3]',
709
+ apply: function(value) { this.hours = +value; }
710
+ },
711
+ 'H': {
712
+ regex: '1?[0-9]|2[0-3]',
713
+ apply: function(value) { this.hours = +value; }
714
+ },
715
+ 'mm': {
716
+ regex: '[0-5][0-9]',
717
+ apply: function(value) { this.minutes = +value; }
718
+ },
719
+ 'm': {
720
+ regex: '[0-9]|[1-5][0-9]',
721
+ apply: function(value) { this.minutes = +value; }
722
+ },
723
+ 'sss': {
724
+ regex: '[0-9][0-9][0-9]',
725
+ apply: function(value) { this.milliseconds = +value; }
726
+ },
727
+ 'ss': {
728
+ regex: '[0-5][0-9]',
729
+ apply: function(value) { this.seconds = +value; }
730
+ },
731
+ 's': {
732
+ regex: '[0-9]|[1-5][0-9]',
733
+ apply: function(value) { this.seconds = +value; }
758
734
  }
759
735
  };
760
736
 
@@ -785,12 +761,13 @@ angular.module('ui.bootstrap.dateparser', [])
785
761
  };
786
762
  }
787
763
 
788
- this.parse = function(input, format) {
764
+ this.parse = function(input, format, baseDate) {
789
765
  if ( !angular.isString(input) || !format ) {
790
766
  return input;
791
767
  }
792
768
 
793
769
  format = $locale.DATETIME_FORMATS[format] || format;
770
+ format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
794
771
 
795
772
  if ( !this.parsers[format] ) {
796
773
  this.parsers[format] = createParser(format);
@@ -802,7 +779,20 @@ angular.module('ui.bootstrap.dateparser', [])
802
779
  results = input.match(regex);
803
780
 
804
781
  if ( results && results.length ) {
805
- var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;
782
+ var fields, dt;
783
+ if (baseDate) {
784
+ fields = {
785
+ year: baseDate.getFullYear(),
786
+ month: baseDate.getMonth(),
787
+ date: baseDate.getDate(),
788
+ hours: baseDate.getHours(),
789
+ minutes: baseDate.getMinutes(),
790
+ seconds: baseDate.getSeconds(),
791
+ milliseconds: baseDate.getMilliseconds()
792
+ };
793
+ } else {
794
+ fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
795
+ }
806
796
 
807
797
  for( var i = 1, n = results.length; i < n; i++ ) {
808
798
  var mapper = map[i-1];
@@ -812,7 +802,8 @@ angular.module('ui.bootstrap.dateparser', [])
812
802
  }
813
803
 
814
804
  if ( isValid(fields.year, fields.month, fields.date) ) {
815
- dt = new Date( fields.year, fields.month, fields.date, fields.hours);
805
+ dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds,
806
+ fields.milliseconds || 0);
816
807
  }
817
808
 
818
809
  return dt;
@@ -822,6 +813,10 @@ angular.module('ui.bootstrap.dateparser', [])
822
813
  // Check if date is valid for specific month (and year for February).
823
814
  // Month: 0 = Jan, 1 = Feb, etc
824
815
  function isValid(year, month, date) {
816
+ if (date < 1) {
817
+ return false;
818
+ }
819
+
825
820
  if ( month === 1 && date > 28) {
826
821
  return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
827
822
  }
@@ -1003,7 +998,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1003
998
  startingDay: 0,
1004
999
  yearRange: 20,
1005
1000
  minDate: null,
1006
- maxDate: null
1001
+ maxDate: null,
1002
+ shortcutPropagation: false
1007
1003
  })
1008
1004
 
1009
1005
  .controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) {
@@ -1015,7 +1011,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1015
1011
 
1016
1012
  // Configuration attributes
1017
1013
  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
1018
- 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) {
1014
+ 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function( key, index ) {
1019
1015
  self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
1020
1016
  });
1021
1017
 
@@ -1032,8 +1028,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1032
1028
  });
1033
1029
 
1034
1030
  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
1031
+ $scope.maxMode = self.maxMode;
1035
1032
  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1036
- this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date();
1033
+
1034
+ if(angular.isDefined($attrs.initDate)) {
1035
+ this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
1036
+ $scope.$parent.$watch($attrs.initDate, function(initDate){
1037
+ if(initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)){
1038
+ self.activeDate = initDate;
1039
+ self.refreshView();
1040
+ }
1041
+ });
1042
+ } else {
1043
+ this.activeDate = new Date();
1044
+ }
1037
1045
 
1038
1046
  $scope.isActive = function(dateObject) {
1039
1047
  if (self.compare(dateObject.date, self.activeDate) === 0) {
@@ -1052,8 +1060,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1052
1060
  };
1053
1061
 
1054
1062
  this.render = function() {
1055
- if ( ngModelCtrl.$modelValue ) {
1056
- var date = new Date( ngModelCtrl.$modelValue ),
1063
+ if ( ngModelCtrl.$viewValue ) {
1064
+ var date = new Date( ngModelCtrl.$viewValue ),
1057
1065
  isValid = !isNaN(date);
1058
1066
 
1059
1067
  if ( isValid ) {
@@ -1070,19 +1078,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1070
1078
  if ( this.element ) {
1071
1079
  this._refreshView();
1072
1080
 
1073
- var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
1081
+ var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1074
1082
  ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
1075
1083
  }
1076
1084
  };
1077
1085
 
1078
1086
  this.createDateObject = function(date, format) {
1079
- var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
1087
+ var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1080
1088
  return {
1081
1089
  date: date,
1082
1090
  label: dateFilter(date, format),
1083
1091
  selected: model && this.compare(date, model) === 0,
1084
1092
  disabled: this.isDisabled(date),
1085
- current: this.compare(date, new Date()) === 0
1093
+ current: this.compare(date, new Date()) === 0,
1094
+ customClass: this.customClass(date)
1086
1095
  };
1087
1096
  };
1088
1097
 
@@ -1090,6 +1099,10 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1090
1099
  return ((this.minDate && this.compare(date, this.minDate) < 0) || (this.maxDate && this.compare(date, this.maxDate) > 0) || ($attrs.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode})));
1091
1100
  };
1092
1101
 
1102
+ this.customClass = function( date ) {
1103
+ return $scope.customClass({date: date, mode: $scope.datepickerMode});
1104
+ };
1105
+
1093
1106
  // Split array into smaller arrays
1094
1107
  this.split = function(arr, size) {
1095
1108
  var arrays = [];
@@ -1101,7 +1114,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1101
1114
 
1102
1115
  $scope.select = function( date ) {
1103
1116
  if ( $scope.datepickerMode === self.minMode ) {
1104
- var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1117
+ var dt = ngModelCtrl.$viewValue ? new Date( ngModelCtrl.$viewValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1105
1118
  dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1106
1119
  ngModelCtrl.$setViewValue( dt );
1107
1120
  ngModelCtrl.$render();
@@ -1148,7 +1161,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1148
1161
  }
1149
1162
 
1150
1163
  evt.preventDefault();
1151
- evt.stopPropagation();
1164
+ if(!self.shortcutPropagation){
1165
+ evt.stopPropagation();
1166
+ }
1152
1167
 
1153
1168
  if (key === 'enter' || key === 'space') {
1154
1169
  if ( self.isDisabled(self.activeDate)) {
@@ -1173,7 +1188,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1173
1188
  templateUrl: 'template/datepicker/datepicker.html',
1174
1189
  scope: {
1175
1190
  datepickerMode: '=?',
1176
- dateDisabled: '&'
1191
+ dateDisabled: '&',
1192
+ customClass: '&',
1193
+ shortcutPropagation: '&?'
1177
1194
  },
1178
1195
  require: ['datepicker', '?^ngModel'],
1179
1196
  controller: 'DatepickerController',
@@ -1248,9 +1265,12 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1248
1265
 
1249
1266
  if ( scope.showWeeks ) {
1250
1267
  scope.weekNumbers = [];
1251
- var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ),
1268
+ var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7,
1252
1269
  numWeeks = scope.rows.length;
1253
- while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {}
1270
+ for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1271
+ scope.weekNumbers.push(
1272
+ getISO8601WeekNumber( scope.rows[curWeek][thursdayIndex].date ));
1273
+ }
1254
1274
  }
1255
1275
  };
1256
1276
 
@@ -1411,6 +1431,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1411
1431
 
1412
1432
  .constant('datepickerPopupConfig', {
1413
1433
  datepickerPopup: 'yyyy-MM-dd',
1434
+ html5Types: {
1435
+ date: 'yyyy-MM-dd',
1436
+ 'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
1437
+ 'month': 'yyyy-MM'
1438
+ },
1414
1439
  currentText: 'Today',
1415
1440
  clearText: 'Clear',
1416
1441
  closeText: 'Done',
@@ -1429,7 +1454,8 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1429
1454
  currentText: '@',
1430
1455
  clearText: '@',
1431
1456
  closeText: '@',
1432
- dateDisabled: '&'
1457
+ dateDisabled: '&',
1458
+ customClass: '&'
1433
1459
  },
1434
1460
  link: function(scope, element, attrs, ngModel) {
1435
1461
  var dateFormat,
@@ -1442,10 +1468,34 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1442
1468
  return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
1443
1469
  };
1444
1470
 
1445
- attrs.$observe('datepickerPopup', function(value) {
1446
- dateFormat = value || datepickerPopupConfig.datepickerPopup;
1447
- ngModel.$render();
1448
- });
1471
+ var isHtml5DateInput = false;
1472
+ if (datepickerPopupConfig.html5Types[attrs.type]) {
1473
+ dateFormat = datepickerPopupConfig.html5Types[attrs.type];
1474
+ isHtml5DateInput = true;
1475
+ } else {
1476
+ dateFormat = attrs.datepickerPopup || datepickerPopupConfig.datepickerPopup;
1477
+ attrs.$observe('datepickerPopup', function(value, oldValue) {
1478
+ var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
1479
+ // Invalidate the $modelValue to ensure that formatters re-run
1480
+ // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
1481
+ if (newDateFormat !== dateFormat) {
1482
+ dateFormat = newDateFormat;
1483
+ ngModel.$modelValue = null;
1484
+
1485
+ if (!dateFormat) {
1486
+ throw new Error('datepickerPopup must have a date format specified.');
1487
+ }
1488
+ }
1489
+ });
1490
+ }
1491
+
1492
+ if (!dateFormat) {
1493
+ throw new Error('datepickerPopup must have a date format specified.');
1494
+ }
1495
+
1496
+ if (isHtml5DateInput && attrs.datepickerPopup) {
1497
+ throw new Error('HTML5 date input types do not support custom formats.');
1498
+ }
1449
1499
 
1450
1500
  // popup element used to display calendar
1451
1501
  var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
@@ -1460,14 +1510,27 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1460
1510
 
1461
1511
  // datepicker element
1462
1512
  var datepickerEl = angular.element(popupEl.children()[0]);
1513
+ if (isHtml5DateInput) {
1514
+ if (attrs.type == 'month') {
1515
+ datepickerEl.attr('datepicker-mode', '"month"');
1516
+ datepickerEl.attr('min-mode', 'month');
1517
+ }
1518
+ }
1519
+
1463
1520
  if ( attrs.datepickerOptions ) {
1464
- angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) {
1521
+ var options = scope.$parent.$eval(attrs.datepickerOptions);
1522
+ if(options.initDate) {
1523
+ scope.initDate = options.initDate;
1524
+ datepickerEl.attr( 'init-date', 'initDate' );
1525
+ delete options.initDate;
1526
+ }
1527
+ angular.forEach(options, function( value, option ) {
1465
1528
  datepickerEl.attr( cameltoDash(option), value );
1466
1529
  });
1467
1530
  }
1468
1531
 
1469
1532
  scope.watchData = {};
1470
- angular.forEach(['minDate', 'maxDate', 'datepickerMode'], function( key ) {
1533
+ angular.forEach(['minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function( key ) {
1471
1534
  if ( attrs[key] ) {
1472
1535
  var getAttribute = $parse(attrs[key]);
1473
1536
  scope.$parent.$watch(getAttribute, function(value){
@@ -1490,36 +1553,78 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1490
1553
  datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
1491
1554
  }
1492
1555
 
1556
+ if (attrs.showWeeks) {
1557
+ datepickerEl.attr('show-weeks', attrs.showWeeks);
1558
+ }
1559
+
1560
+ if (attrs.customClass){
1561
+ datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
1562
+ }
1563
+
1493
1564
  function parseDate(viewValue) {
1565
+ if (angular.isNumber(viewValue)) {
1566
+ // presumably timestamp to date object
1567
+ viewValue = new Date(viewValue);
1568
+ }
1569
+
1494
1570
  if (!viewValue) {
1495
- ngModel.$setValidity('date', true);
1496
1571
  return null;
1497
1572
  } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
1498
- ngModel.$setValidity('date', true);
1499
1573
  return viewValue;
1500
1574
  } else if (angular.isString(viewValue)) {
1501
- var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
1575
+ var date = dateParser.parse(viewValue, dateFormat, scope.date) || new Date(viewValue);
1502
1576
  if (isNaN(date)) {
1503
- ngModel.$setValidity('date', false);
1504
1577
  return undefined;
1505
1578
  } else {
1506
- ngModel.$setValidity('date', true);
1507
1579
  return date;
1508
1580
  }
1509
1581
  } else {
1510
- ngModel.$setValidity('date', false);
1511
1582
  return undefined;
1512
1583
  }
1513
1584
  }
1514
- ngModel.$parsers.unshift(parseDate);
1585
+
1586
+ function validator(modelValue, viewValue) {
1587
+ var value = modelValue || viewValue;
1588
+ if (angular.isNumber(value)) {
1589
+ value = new Date(value);
1590
+ }
1591
+ if (!value) {
1592
+ return true;
1593
+ } else if (angular.isDate(value) && !isNaN(value)) {
1594
+ return true;
1595
+ } else if (angular.isString(value)) {
1596
+ var date = dateParser.parse(value, dateFormat) || new Date(value);
1597
+ return !isNaN(date);
1598
+ } else {
1599
+ return false;
1600
+ }
1601
+ }
1602
+
1603
+ if (!isHtml5DateInput) {
1604
+ // Internal API to maintain the correct ng-invalid-[key] class
1605
+ ngModel.$$parserName = 'date';
1606
+ ngModel.$validators.date = validator;
1607
+ ngModel.$parsers.unshift(parseDate);
1608
+ ngModel.$formatters.push(function (value) {
1609
+ scope.date = value;
1610
+ return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
1611
+ });
1612
+ }
1613
+ else {
1614
+ ngModel.$formatters.push(function (value) {
1615
+ scope.date = value;
1616
+ return value;
1617
+ });
1618
+ }
1515
1619
 
1516
1620
  // Inner change
1517
1621
  scope.dateSelection = function(dt) {
1518
1622
  if (angular.isDefined(dt)) {
1519
1623
  scope.date = dt;
1520
1624
  }
1521
- ngModel.$setViewValue(scope.date);
1522
- ngModel.$render();
1625
+ var date = scope.date ? dateFilter(scope.date, dateFormat) : '';
1626
+ element.val(date);
1627
+ ngModel.$setViewValue(date);
1523
1628
 
1524
1629
  if ( closeOnDateSelection ) {
1525
1630
  scope.isOpen = false;
@@ -1527,19 +1632,11 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1527
1632
  }
1528
1633
  };
1529
1634
 
1530
- element.bind('input change keyup', function() {
1531
- scope.$apply(function() {
1532
- scope.date = ngModel.$modelValue;
1533
- });
1635
+ // Detect changes in the view from the text box
1636
+ ngModel.$viewChangeListeners.push(function () {
1637
+ scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date) || new Date(ngModel.$viewValue);
1534
1638
  });
1535
1639
 
1536
- // Outter change
1537
- ngModel.$render = function() {
1538
- var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
1539
- element.val(date);
1540
- scope.date = parseDate( ngModel.$modelValue );
1541
- };
1542
-
1543
1640
  var documentClickBind = function(event) {
1544
1641
  if (scope.isOpen && event.target !== element[0]) {
1545
1642
  scope.$apply(function() {
@@ -1556,7 +1653,9 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1556
1653
  scope.keydown = function(evt) {
1557
1654
  if (evt.which === 27) {
1558
1655
  evt.preventDefault();
1559
- evt.stopPropagation();
1656
+ if (scope.isOpen) {
1657
+ evt.stopPropagation();
1658
+ }
1560
1659
  scope.close();
1561
1660
  } else if (evt.which === 40 && !scope.isOpen) {
1562
1661
  scope.isOpen = true;
@@ -1578,8 +1677,8 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1578
1677
  scope.select = function( date ) {
1579
1678
  if (date === 'today') {
1580
1679
  var today = new Date();
1581
- if (angular.isDate(ngModel.$modelValue)) {
1582
- date = new Date(ngModel.$modelValue);
1680
+ if (angular.isDate(scope.date)) {
1681
+ date = new Date(scope.date);
1583
1682
  date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
1584
1683
  } else {
1585
1684
  date = new Date(today.setHours(0, 0, 0, 0));
@@ -1627,13 +1726,13 @@ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepi
1627
1726
  };
1628
1727
  });
1629
1728
 
1630
- angular.module('ui.bootstrap.dropdown', [])
1729
+ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1631
1730
 
1632
1731
  .constant('dropdownConfig', {
1633
1732
  openClass: 'open'
1634
1733
  })
1635
1734
 
1636
- .service('dropdownService', ['$document', function($document) {
1735
+ .service('dropdownService', ['$document', '$rootScope', function($document, $rootScope) {
1637
1736
  var openScope = null;
1638
1737
 
1639
1738
  this.open = function( dropdownScope ) {
@@ -1662,14 +1761,23 @@ angular.module('ui.bootstrap.dropdown', [])
1662
1761
  // unbound this event handler. So check openScope before proceeding.
1663
1762
  if (!openScope) { return; }
1664
1763
 
1764
+ if( evt && openScope.getAutoClose() === 'disabled' ) { return ; }
1765
+
1665
1766
  var toggleElement = openScope.getToggleElement();
1666
1767
  if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) {
1667
1768
  return;
1668
1769
  }
1669
1770
 
1670
- openScope.$apply(function() {
1671
- openScope.isOpen = false;
1672
- });
1771
+ var $element = openScope.getElement();
1772
+ if( evt && openScope.getAutoClose() === 'outsideClick' && $element && $element[0].contains(evt.target) ) {
1773
+ return;
1774
+ }
1775
+
1776
+ openScope.isOpen = false;
1777
+
1778
+ if (!$rootScope.$$phase) {
1779
+ openScope.$apply();
1780
+ }
1673
1781
  };
1674
1782
 
1675
1783
  var escapeKeyBind = function( evt ) {
@@ -1680,13 +1788,14 @@ angular.module('ui.bootstrap.dropdown', [])
1680
1788
  };
1681
1789
  }])
1682
1790
 
1683
- .controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
1791
+ .controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', '$position', '$document', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate, $position, $document) {
1684
1792
  var self = this,
1685
1793
  scope = $scope.$new(), // create a child scope so we are not polluting original one
1686
1794
  openClass = dropdownConfig.openClass,
1687
1795
  getIsOpen,
1688
1796
  setIsOpen = angular.noop,
1689
- toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
1797
+ toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
1798
+ appendToBody = false;
1690
1799
 
1691
1800
  this.init = function( element ) {
1692
1801
  self.$element = element;
@@ -1699,6 +1808,15 @@ angular.module('ui.bootstrap.dropdown', [])
1699
1808
  scope.isOpen = !!value;
1700
1809
  });
1701
1810
  }
1811
+
1812
+ appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
1813
+
1814
+ if ( appendToBody && self.dropdownMenu ) {
1815
+ $document.find('body').append( self.dropdownMenu );
1816
+ element.on('$destroy', function handleDestroyEvent() {
1817
+ self.dropdownMenu.remove();
1818
+ });
1819
+ }
1702
1820
  };
1703
1821
 
1704
1822
  this.toggle = function( open ) {
@@ -1714,6 +1832,14 @@ angular.module('ui.bootstrap.dropdown', [])
1714
1832
  return self.toggleElement;
1715
1833
  };
1716
1834
 
1835
+ scope.getAutoClose = function() {
1836
+ return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
1837
+ };
1838
+
1839
+ scope.getElement = function() {
1840
+ return self.$element;
1841
+ };
1842
+
1717
1843
  scope.focusToggleElement = function() {
1718
1844
  if ( self.toggleElement ) {
1719
1845
  self.toggleElement[0].focus();
@@ -1721,6 +1847,15 @@ angular.module('ui.bootstrap.dropdown', [])
1721
1847
  };
1722
1848
 
1723
1849
  scope.$watch('isOpen', function( isOpen, wasOpen ) {
1850
+ if ( appendToBody && self.dropdownMenu ) {
1851
+ var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true);
1852
+ self.dropdownMenu.css({
1853
+ top: pos.top + 'px',
1854
+ left: pos.left + 'px',
1855
+ display: isOpen ? 'block' : 'none'
1856
+ });
1857
+ }
1858
+
1724
1859
  $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
1725
1860
 
1726
1861
  if ( isOpen ) {
@@ -1754,6 +1889,19 @@ angular.module('ui.bootstrap.dropdown', [])
1754
1889
  };
1755
1890
  })
1756
1891
 
1892
+ .directive('dropdownMenu', function() {
1893
+ return {
1894
+ restrict: 'AC',
1895
+ require: '?^dropdown',
1896
+ link: function(scope, element, attrs, dropdownCtrl) {
1897
+ if ( !dropdownCtrl ) {
1898
+ return;
1899
+ }
1900
+ dropdownCtrl.dropdownMenu = element;
1901
+ }
1902
+ };
1903
+ })
1904
+
1757
1905
  .directive('dropdownToggle', function() {
1758
1906
  return {
1759
1907
  require: '?^dropdown',
@@ -1789,7 +1937,7 @@ angular.module('ui.bootstrap.dropdown', [])
1789
1937
  };
1790
1938
  });
1791
1939
 
1792
- angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1940
+ angular.module('ui.bootstrap.modal', [])
1793
1941
 
1794
1942
  /**
1795
1943
  * A helper, internal data structure that acts as a map but also allows getting / removing
@@ -1853,20 +2001,23 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1853
2001
  restrict: 'EA',
1854
2002
  replace: true,
1855
2003
  templateUrl: 'template/modal/backdrop.html',
1856
- link: function (scope, element, attrs) {
1857
- scope.backdropClass = attrs.backdropClass || '';
1858
-
1859
- scope.animate = false;
1860
-
1861
- //trigger CSS transitions
1862
- $timeout(function () {
1863
- scope.animate = true;
1864
- });
2004
+ compile: function (tElement, tAttrs) {
2005
+ tElement.addClass(tAttrs.backdropClass);
2006
+ return linkFn;
1865
2007
  }
1866
2008
  };
2009
+
2010
+ function linkFn(scope, element, attrs) {
2011
+ scope.animate = false;
2012
+
2013
+ //trigger CSS transitions
2014
+ $timeout(function () {
2015
+ scope.animate = true;
2016
+ });
2017
+ }
1867
2018
  }])
1868
2019
 
1869
- .directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
2020
+ .directive('modalWindow', ['$modalStack', '$q', function ($modalStack, $q) {
1870
2021
  return {
1871
2022
  restrict: 'EA',
1872
2023
  scope: {
@@ -1882,10 +2033,35 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1882
2033
  element.addClass(attrs.windowClass || '');
1883
2034
  scope.size = attrs.size;
1884
2035
 
1885
- $timeout(function () {
2036
+ scope.close = function (evt) {
2037
+ var modal = $modalStack.getTop();
2038
+ if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
2039
+ evt.preventDefault();
2040
+ evt.stopPropagation();
2041
+ $modalStack.dismiss(modal.key, 'backdrop click');
2042
+ }
2043
+ };
2044
+
2045
+ // This property is only added to the scope for the purpose of detecting when this directive is rendered.
2046
+ // We can detect that by using this property in the template associated with this directive and then use
2047
+ // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
2048
+ scope.$isRendered = true;
2049
+
2050
+ // Deferred object that will be resolved when this modal is render.
2051
+ var modalRenderDeferObj = $q.defer();
2052
+ // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
2053
+ // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
2054
+ attrs.$observe('modalRender', function (value) {
2055
+ if (value == 'true') {
2056
+ modalRenderDeferObj.resolve();
2057
+ }
2058
+ });
2059
+
2060
+ modalRenderDeferObj.promise.then(function () {
1886
2061
  // trigger CSS transitions
1887
2062
  scope.animate = true;
1888
2063
 
2064
+ var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]');
1889
2065
  /**
1890
2066
  * Auto-focusing of a freshly-opened modal element causes any child elements
1891
2067
  * with the autofocus attribute to lose focus. This is an issue on touch
@@ -1894,23 +2070,33 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1894
2070
  * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
1895
2071
  * the modal element if the modal does not contain an autofocus element.
1896
2072
  */
1897
- if (!element[0].querySelectorAll('[autofocus]').length) {
2073
+ if (inputsWithAutofocus.length) {
2074
+ inputsWithAutofocus[0].focus();
2075
+ } else {
1898
2076
  element[0].focus();
1899
2077
  }
1900
- });
1901
2078
 
1902
- scope.close = function (evt) {
2079
+ // Notify {@link $modalStack} that modal is rendered.
1903
2080
  var modal = $modalStack.getTop();
1904
- if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
1905
- evt.preventDefault();
1906
- evt.stopPropagation();
1907
- $modalStack.dismiss(modal.key, 'backdrop click');
2081
+ if (modal) {
2082
+ $modalStack.modalRendered(modal.key);
1908
2083
  }
1909
- };
2084
+ });
1910
2085
  }
1911
2086
  };
1912
2087
  }])
1913
2088
 
2089
+ .directive('modalAnimationClass', [
2090
+ function () {
2091
+ return {
2092
+ compile: function (tElement, tAttrs) {
2093
+ if (tAttrs.modalAnimation) {
2094
+ tElement.addClass(tAttrs.modalAnimationClass);
2095
+ }
2096
+ }
2097
+ };
2098
+ }])
2099
+
1914
2100
  .directive('modalTransclude', function () {
1915
2101
  return {
1916
2102
  link: function($scope, $element, $attrs, controller, $transclude) {
@@ -1922,8 +2108,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1922
2108
  };
1923
2109
  })
1924
2110
 
1925
- .factory('$modalStack', ['$transition', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
1926
- function ($transition, $timeout, $document, $compile, $rootScope, $$stackedMap) {
2111
+ .factory('$modalStack', ['$animate', '$timeout', '$document', '$compile', '$rootScope', '$$stackedMap',
2112
+ function ($animate, $timeout, $document, $compile, $rootScope, $$stackedMap) {
1927
2113
 
1928
2114
  var OPENED_MODAL_CLASS = 'modal-open';
1929
2115
 
@@ -1957,8 +2143,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1957
2143
  openedWindows.remove(modalInstance);
1958
2144
 
1959
2145
  //remove window DOM element
1960
- removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() {
1961
- modalWindow.modalScope.$destroy();
2146
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
1962
2147
  body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
1963
2148
  checkRemoveBackdrop();
1964
2149
  });
@@ -1968,8 +2153,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1968
2153
  //remove backdrop if no longer needed
1969
2154
  if (backdropDomEl && backdropIndex() == -1) {
1970
2155
  var backdropScopeRef = backdropScope;
1971
- removeAfterAnimate(backdropDomEl, backdropScope, 150, function () {
1972
- backdropScopeRef.$destroy();
2156
+ removeAfterAnimate(backdropDomEl, backdropScope, function () {
1973
2157
  backdropScopeRef = null;
1974
2158
  });
1975
2159
  backdropDomEl = undefined;
@@ -1977,19 +2161,14 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1977
2161
  }
1978
2162
  }
1979
2163
 
1980
- function removeAfterAnimate(domEl, scope, emulateTime, done) {
2164
+ function removeAfterAnimate(domEl, scope, done) {
1981
2165
  // Closing animation
1982
2166
  scope.animate = false;
1983
2167
 
1984
- var transitionEndEventName = $transition.transitionEndEventName;
1985
- if (transitionEndEventName) {
2168
+ if (domEl.attr('modal-animation') && $animate.enabled()) {
1986
2169
  // transition out
1987
- var timeout = $timeout(afterAnimating, emulateTime);
1988
-
1989
- domEl.bind(transitionEndEventName, function () {
1990
- $timeout.cancel(timeout);
1991
- afterAnimating();
1992
- scope.$apply();
2170
+ domEl.one('$animate:close', function closeFn() {
2171
+ $rootScope.$evalAsync(afterAnimating);
1993
2172
  });
1994
2173
  } else {
1995
2174
  // Ensure this call is async
@@ -2003,6 +2182,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2003
2182
  afterAnimating.done = true;
2004
2183
 
2005
2184
  domEl.remove();
2185
+ scope.$destroy();
2006
2186
  if (done) {
2007
2187
  done();
2008
2188
  }
@@ -2025,8 +2205,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2025
2205
 
2026
2206
  $modalStack.open = function (modalInstance, modal) {
2027
2207
 
2208
+ var modalOpener = $document[0].activeElement;
2209
+
2028
2210
  openedWindows.add(modalInstance, {
2029
2211
  deferred: modal.deferred,
2212
+ renderDeferred: modal.renderDeferred,
2030
2213
  modalScope: modal.scope,
2031
2214
  backdrop: modal.backdrop,
2032
2215
  keyboard: modal.keyboard
@@ -2038,13 +2221,16 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2038
2221
  if (currBackdropIndex >= 0 && !backdropDomEl) {
2039
2222
  backdropScope = $rootScope.$new(true);
2040
2223
  backdropScope.index = currBackdropIndex;
2041
- var angularBackgroundDomEl = angular.element('<div modal-backdrop></div>');
2224
+ var angularBackgroundDomEl = angular.element('<div modal-backdrop="modal-backdrop"></div>');
2042
2225
  angularBackgroundDomEl.attr('backdrop-class', modal.backdropClass);
2226
+ if (modal.animation) {
2227
+ angularBackgroundDomEl.attr('modal-animation', 'true');
2228
+ }
2043
2229
  backdropDomEl = $compile(angularBackgroundDomEl)(backdropScope);
2044
2230
  body.append(backdropDomEl);
2045
2231
  }
2046
2232
 
2047
- var angularDomEl = angular.element('<div modal-window></div>');
2233
+ var angularDomEl = angular.element('<div modal-window="modal-window"></div>');
2048
2234
  angularDomEl.attr({
2049
2235
  'template-url': modal.windowTemplateUrl,
2050
2236
  'window-class': modal.windowClass,
@@ -2052,33 +2238,46 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2052
2238
  'index': openedWindows.length() - 1,
2053
2239
  'animate': 'animate'
2054
2240
  }).html(modal.content);
2241
+ if (modal.animation) {
2242
+ angularDomEl.attr('modal-animation', 'true');
2243
+ }
2055
2244
 
2056
2245
  var modalDomEl = $compile(angularDomEl)(modal.scope);
2057
2246
  openedWindows.top().value.modalDomEl = modalDomEl;
2247
+ openedWindows.top().value.modalOpener = modalOpener;
2058
2248
  body.append(modalDomEl);
2059
2249
  body.addClass(OPENED_MODAL_CLASS);
2060
2250
  };
2061
2251
 
2252
+ function broadcastClosing(modalWindow, resultOrReason, closing) {
2253
+ return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
2254
+ }
2255
+
2062
2256
  $modalStack.close = function (modalInstance, result) {
2063
2257
  var modalWindow = openedWindows.get(modalInstance);
2064
- if (modalWindow) {
2258
+ if (modalWindow && broadcastClosing(modalWindow, result, true)) {
2065
2259
  modalWindow.value.deferred.resolve(result);
2066
2260
  removeModalWindow(modalInstance);
2261
+ modalWindow.value.modalOpener.focus();
2262
+ return true;
2067
2263
  }
2264
+ return !modalWindow;
2068
2265
  };
2069
2266
 
2070
2267
  $modalStack.dismiss = function (modalInstance, reason) {
2071
2268
  var modalWindow = openedWindows.get(modalInstance);
2072
- if (modalWindow) {
2269
+ if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
2073
2270
  modalWindow.value.deferred.reject(reason);
2074
2271
  removeModalWindow(modalInstance);
2272
+ modalWindow.value.modalOpener.focus();
2273
+ return true;
2075
2274
  }
2275
+ return !modalWindow;
2076
2276
  };
2077
2277
 
2078
2278
  $modalStack.dismissAll = function (reason) {
2079
2279
  var topModal = this.getTop();
2080
- while (topModal) {
2081
- this.dismiss(topModal.key, reason);
2280
+ while (topModal && this.dismiss(topModal.key, reason)) {
2082
2281
  topModal = this.getTop();
2083
2282
  }
2084
2283
  };
@@ -2087,6 +2286,13 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2087
2286
  return openedWindows.top();
2088
2287
  };
2089
2288
 
2289
+ $modalStack.modalRendered = function (modalInstance) {
2290
+ var modalWindow = openedWindows.get(modalInstance);
2291
+ if (modalWindow) {
2292
+ modalWindow.value.renderDeferred.resolve();
2293
+ }
2294
+ };
2295
+
2090
2296
  return $modalStack;
2091
2297
  }])
2092
2298
 
@@ -2094,20 +2300,18 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2094
2300
 
2095
2301
  var $modalProvider = {
2096
2302
  options: {
2097
- backdrop: true, //can be also false or 'static'
2303
+ animation: true,
2304
+ backdrop: true, //can also be false or 'static'
2098
2305
  keyboard: true
2099
2306
  },
2100
- $get: ['$injector', '$rootScope', '$q', '$http', '$templateCache', '$controller', '$modalStack',
2101
- function ($injector, $rootScope, $q, $http, $templateCache, $controller, $modalStack) {
2307
+ $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$modalStack',
2308
+ function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack) {
2102
2309
 
2103
2310
  var $modal = {};
2104
2311
 
2105
2312
  function getTemplatePromise(options) {
2106
2313
  return options.template ? $q.when(options.template) :
2107
- $http.get(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl,
2108
- {cache: $templateCache}).then(function (result) {
2109
- return result.data;
2110
- });
2314
+ $templateRequest(angular.isFunction(options.templateUrl) ? (options.templateUrl)() : options.templateUrl);
2111
2315
  }
2112
2316
 
2113
2317
  function getResolvePromises(resolves) {
@@ -2124,16 +2328,18 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2124
2328
 
2125
2329
  var modalResultDeferred = $q.defer();
2126
2330
  var modalOpenedDeferred = $q.defer();
2331
+ var modalRenderDeferred = $q.defer();
2127
2332
 
2128
2333
  //prepare an instance of a modal to be injected into controllers and returned to a caller
2129
2334
  var modalInstance = {
2130
2335
  result: modalResultDeferred.promise,
2131
2336
  opened: modalOpenedDeferred.promise,
2337
+ rendered: modalRenderDeferred.promise,
2132
2338
  close: function (result) {
2133
- $modalStack.close(modalInstance, result);
2339
+ return $modalStack.close(modalInstance, result);
2134
2340
  },
2135
2341
  dismiss: function (reason) {
2136
- $modalStack.dismiss(modalInstance, reason);
2342
+ return $modalStack.dismiss(modalInstance, reason);
2137
2343
  }
2138
2344
  };
2139
2345
 
@@ -2176,7 +2382,9 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2176
2382
  $modalStack.open(modalInstance, {
2177
2383
  scope: modalScope,
2178
2384
  deferred: modalResultDeferred,
2385
+ renderDeferred: modalRenderDeferred,
2179
2386
  content: tplAndVars[0],
2387
+ animation: modalOptions.animation,
2180
2388
  backdrop: modalOptions.backdrop,
2181
2389
  keyboard: modalOptions.keyboard,
2182
2390
  backdropClass: modalOptions.backdropClass,
@@ -2191,8 +2399,8 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
2191
2399
 
2192
2400
  templateAndResolvePromise.then(function () {
2193
2401
  modalOpenedDeferred.resolve(true);
2194
- }, function () {
2195
- modalOpenedDeferred.reject(false);
2402
+ }, function (reason) {
2403
+ modalOpenedDeferred.reject(reason);
2196
2404
  });
2197
2405
 
2198
2406
  return modalInstance;
@@ -2228,6 +2436,20 @@ angular.module('ui.bootstrap.pagination', [])
2228
2436
  } else {
2229
2437
  this.itemsPerPage = config.itemsPerPage;
2230
2438
  }
2439
+
2440
+ $scope.$watch('totalItems', function() {
2441
+ $scope.totalPages = self.calculateTotalPages();
2442
+ });
2443
+
2444
+ $scope.$watch('totalPages', function(value) {
2445
+ setNumPages($scope.$parent, value); // Readonly variable
2446
+
2447
+ if ( $scope.page > value ) {
2448
+ $scope.selectPage(value);
2449
+ } else {
2450
+ ngModelCtrl.$render();
2451
+ }
2452
+ });
2231
2453
  };
2232
2454
 
2233
2455
  this.calculateTotalPages = function() {
@@ -2239,8 +2461,11 @@ angular.module('ui.bootstrap.pagination', [])
2239
2461
  $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
2240
2462
  };
2241
2463
 
2242
- $scope.selectPage = function(page) {
2464
+ $scope.selectPage = function(page, evt) {
2243
2465
  if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) {
2466
+ if (evt && evt.target) {
2467
+ evt.target.blur();
2468
+ }
2244
2469
  ngModelCtrl.$setViewValue(page);
2245
2470
  ngModelCtrl.$render();
2246
2471
  }
@@ -2255,20 +2480,6 @@ angular.module('ui.bootstrap.pagination', [])
2255
2480
  $scope.noNext = function() {
2256
2481
  return $scope.page === $scope.totalPages;
2257
2482
  };
2258
-
2259
- $scope.$watch('totalItems', function() {
2260
- $scope.totalPages = self.calculateTotalPages();
2261
- });
2262
-
2263
- $scope.$watch('totalPages', function(value) {
2264
- setNumPages($scope.$parent, value); // Readonly variable
2265
-
2266
- if ( $scope.page > value ) {
2267
- $scope.selectPage(value);
2268
- } else {
2269
- ngModelCtrl.$render();
2270
- }
2271
- });
2272
2483
  }])
2273
2484
 
2274
2485
  .constant('paginationConfig', {
@@ -2436,7 +2647,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2436
2647
  var defaultOptions = {
2437
2648
  placement: 'top',
2438
2649
  animation: true,
2439
- popupDelay: 0
2650
+ popupDelay: 0,
2651
+ useContentExp: false
2440
2652
  };
2441
2653
 
2442
2654
  // Default hide triggers for each show trigger
@@ -2487,8 +2699,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2487
2699
  * TODO support multiple triggers
2488
2700
  */
2489
2701
  this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $document, $position, $interpolate ) {
2490
- return function $tooltip ( type, prefix, defaultTriggerShow ) {
2491
- var options = angular.extend( {}, defaultOptions, globalOptions );
2702
+ return function $tooltip ( type, prefix, defaultTriggerShow, options ) {
2703
+ options = angular.extend( {}, defaultOptions, globalOptions, options );
2492
2704
 
2493
2705
  /**
2494
2706
  * Returns an object of show and hide triggers.
@@ -2520,10 +2732,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2520
2732
  var template =
2521
2733
  '<div '+ directiveName +'-popup '+
2522
2734
  'title="'+startSym+'title'+endSym+'" '+
2523
- 'content="'+startSym+'content'+endSym+'" '+
2735
+ (options.useContentExp ?
2736
+ 'content-exp="contentExp()" ' :
2737
+ 'content="'+startSym+'content'+endSym+'" ') +
2524
2738
  'placement="'+startSym+'placement'+endSym+'" '+
2739
+ 'popup-class="'+startSym+'popupClass'+endSym+'" '+
2525
2740
  'animation="animation" '+
2526
2741
  'is-open="isOpen"'+
2742
+ 'origin-scope="origScope" '+
2527
2743
  '>'+
2528
2744
  '</div>';
2529
2745
 
@@ -2532,7 +2748,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2532
2748
  compile: function (tElem, tAttrs) {
2533
2749
  var tooltipLinker = $compile( template );
2534
2750
 
2535
- return function link ( scope, element, attrs ) {
2751
+ return function link ( scope, element, attrs, tooltipCtrl ) {
2536
2752
  var tooltip;
2537
2753
  var tooltipLinkedScope;
2538
2754
  var transitionTimeout;
@@ -2543,6 +2759,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2543
2759
  var ttScope = scope.$new(true);
2544
2760
 
2545
2761
  var positionTooltip = function () {
2762
+ if (!tooltip) { return; }
2546
2763
 
2547
2764
  var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
2548
2765
  ttPosition.top += 'px';
@@ -2552,6 +2769,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2552
2769
  tooltip.css( ttPosition );
2553
2770
  };
2554
2771
 
2772
+ // Set up the correct scope to allow transclusion later
2773
+ ttScope.origScope = scope;
2774
+
2555
2775
  // By default, the tooltip is not open.
2556
2776
  // TODO add ability to start tooltip opened
2557
2777
  ttScope.isOpen = false;
@@ -2603,7 +2823,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2603
2823
  }
2604
2824
 
2605
2825
  // Don't show empty tooltips.
2606
- if ( ! ttScope.content ) {
2826
+ if ( !(options.useContentExp ? ttScope.contentExp() : ttScope.content) ) {
2607
2827
  return angular.noop;
2608
2828
  }
2609
2829
 
@@ -2617,7 +2837,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2617
2837
 
2618
2838
  // And show the tooltip.
2619
2839
  ttScope.isOpen = true;
2620
- ttScope.$digest(); // digest required as $apply is not called
2840
+ ttScope.$apply(); // digest required as $apply is not called
2621
2841
 
2622
2842
  // Return positioning function as promise callback for correct
2623
2843
  // positioning after draw.
@@ -2658,6 +2878,18 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2658
2878
  element.after( tooltip );
2659
2879
  }
2660
2880
  });
2881
+
2882
+ tooltipLinkedScope.$watch(function () {
2883
+ $timeout(positionTooltip, 0, false);
2884
+ });
2885
+
2886
+ if (options.useContentExp) {
2887
+ tooltipLinkedScope.$watch('contentExp()', function (val) {
2888
+ if (!val && ttScope.isOpen ) {
2889
+ hide();
2890
+ }
2891
+ });
2892
+ }
2661
2893
  }
2662
2894
 
2663
2895
  function removeTooltip() {
@@ -2673,17 +2905,30 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2673
2905
  }
2674
2906
 
2675
2907
  function prepareTooltip() {
2908
+ prepPopupClass();
2676
2909
  prepPlacement();
2677
2910
  prepPopupDelay();
2678
2911
  }
2679
2912
 
2913
+ ttScope.contentExp = function () {
2914
+ return scope.$eval(attrs[type]);
2915
+ };
2916
+
2680
2917
  /**
2681
2918
  * Observe the relevant attributes.
2682
2919
  */
2683
- attrs.$observe( type, function ( val ) {
2684
- ttScope.content = val;
2920
+ if (!options.useContentExp) {
2921
+ attrs.$observe( type, function ( val ) {
2922
+ ttScope.content = val;
2923
+
2924
+ if (!val && ttScope.isOpen ) {
2925
+ hide();
2926
+ }
2927
+ });
2928
+ }
2685
2929
 
2686
- if (!val && ttScope.isOpen ) {
2930
+ attrs.$observe( 'disabled', function ( val ) {
2931
+ if (val && ttScope.isOpen ) {
2687
2932
  hide();
2688
2933
  }
2689
2934
  });
@@ -2692,6 +2937,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2692
2937
  ttScope.title = val;
2693
2938
  });
2694
2939
 
2940
+ function prepPopupClass() {
2941
+ ttScope.popupClass = attrs[prefix + 'Class'];
2942
+ }
2943
+
2695
2944
  function prepPlacement() {
2696
2945
  var val = attrs[ prefix + 'Placement' ];
2697
2946
  ttScope.placement = angular.isDefined( val ) ? val : options.placement;
@@ -2755,11 +3004,101 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2755
3004
  }];
2756
3005
  })
2757
3006
 
3007
+ // This is mostly ngInclude code but with a custom scope
3008
+ .directive( 'tooltipTemplateTransclude', [
3009
+ '$animate', '$sce', '$compile', '$templateRequest',
3010
+ function ($animate , $sce , $compile , $templateRequest) {
3011
+ return {
3012
+ link: function ( scope, elem, attrs ) {
3013
+ var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
3014
+
3015
+ var changeCounter = 0,
3016
+ currentScope,
3017
+ previousElement,
3018
+ currentElement;
3019
+
3020
+ var cleanupLastIncludeContent = function() {
3021
+ if (previousElement) {
3022
+ previousElement.remove();
3023
+ previousElement = null;
3024
+ }
3025
+ if (currentScope) {
3026
+ currentScope.$destroy();
3027
+ currentScope = null;
3028
+ }
3029
+ if (currentElement) {
3030
+ $animate.leave(currentElement).then(function() {
3031
+ previousElement = null;
3032
+ });
3033
+ previousElement = currentElement;
3034
+ currentElement = null;
3035
+ }
3036
+ };
3037
+
3038
+ scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function (src) {
3039
+ var thisChangeId = ++changeCounter;
3040
+
3041
+ if (src) {
3042
+ //set the 2nd param to true to ignore the template request error so that the inner
3043
+ //contents and scope can be cleaned up.
3044
+ $templateRequest(src, true).then(function(response) {
3045
+ if (thisChangeId !== changeCounter) { return; }
3046
+ var newScope = origScope.$new();
3047
+ var template = response;
3048
+
3049
+ var clone = $compile(template)(newScope, function(clone) {
3050
+ cleanupLastIncludeContent();
3051
+ $animate.enter(clone, elem);
3052
+ });
3053
+
3054
+ currentScope = newScope;
3055
+ currentElement = clone;
3056
+
3057
+ currentScope.$emit('$includeContentLoaded', src);
3058
+ }, function() {
3059
+ if (thisChangeId === changeCounter) {
3060
+ cleanupLastIncludeContent();
3061
+ scope.$emit('$includeContentError', src);
3062
+ }
3063
+ });
3064
+ scope.$emit('$includeContentRequested', src);
3065
+ } else {
3066
+ cleanupLastIncludeContent();
3067
+ }
3068
+ });
3069
+
3070
+ scope.$on('$destroy', cleanupLastIncludeContent);
3071
+ }
3072
+ };
3073
+ }])
3074
+
3075
+ /**
3076
+ * Note that it's intentional that these classes are *not* applied through $animate.
3077
+ * They must not be animated as they're expected to be present on the tooltip on
3078
+ * initialization.
3079
+ */
3080
+ .directive('tooltipClasses', function () {
3081
+ return {
3082
+ restrict: 'A',
3083
+ link: function (scope, element, attrs) {
3084
+ if (scope.placement) {
3085
+ element.addClass(scope.placement);
3086
+ }
3087
+ if (scope.popupClass) {
3088
+ element.addClass(scope.popupClass);
3089
+ }
3090
+ if (scope.animation()) {
3091
+ element.addClass(attrs.tooltipAnimationClass);
3092
+ }
3093
+ }
3094
+ };
3095
+ })
3096
+
2758
3097
  .directive( 'tooltipPopup', function () {
2759
3098
  return {
2760
3099
  restrict: 'EA',
2761
3100
  replace: true,
2762
- scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
3101
+ scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
2763
3102
  templateUrl: 'template/tooltip/tooltip-popup.html'
2764
3103
  };
2765
3104
  })
@@ -2768,16 +3107,56 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2768
3107
  return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
2769
3108
  }])
2770
3109
 
3110
+ .directive( 'tooltipTemplatePopup', function () {
3111
+ return {
3112
+ restrict: 'EA',
3113
+ replace: true,
3114
+ scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
3115
+ originScope: '&' },
3116
+ templateUrl: 'template/tooltip/tooltip-template-popup.html'
3117
+ };
3118
+ })
3119
+
3120
+ .directive( 'tooltipTemplate', [ '$tooltip', function ( $tooltip ) {
3121
+ return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
3122
+ useContentExp: true
3123
+ });
3124
+ }])
3125
+
3126
+ .directive( 'tooltipHtmlPopup', function () {
3127
+ return {
3128
+ restrict: 'EA',
3129
+ replace: true,
3130
+ scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
3131
+ templateUrl: 'template/tooltip/tooltip-html-popup.html'
3132
+ };
3133
+ })
3134
+
3135
+ .directive( 'tooltipHtml', [ '$tooltip', function ( $tooltip ) {
3136
+ return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', {
3137
+ useContentExp: true
3138
+ });
3139
+ }])
3140
+
3141
+ /*
3142
+ Deprecated
3143
+ */
2771
3144
  .directive( 'tooltipHtmlUnsafePopup', function () {
2772
3145
  return {
2773
3146
  restrict: 'EA',
2774
3147
  replace: true,
2775
- scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
3148
+ scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
2776
3149
  templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
2777
3150
  };
2778
3151
  })
2779
3152
 
2780
- .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
3153
+ .value('tooltipHtmlUnsafeSuppressDeprecated', false)
3154
+ .directive( 'tooltipHtmlUnsafe', [
3155
+ '$tooltip', 'tooltipHtmlUnsafeSuppressDeprecated', '$log',
3156
+ function ( $tooltip , tooltipHtmlUnsafeSuppressDeprecated , $log) {
3157
+ if (!tooltipHtmlUnsafeSuppressDeprecated) {
3158
+ $log.warn('tooltip-html-unsafe is now deprecated. Use tooltip-html or tooltip-template instead.');
3159
+ }
2781
3160
  return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
2782
3161
  }]);
2783
3162
 
@@ -2788,11 +3167,27 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2788
3167
  */
2789
3168
  angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
2790
3169
 
3170
+ .directive( 'popoverTemplatePopup', function () {
3171
+ return {
3172
+ restrict: 'EA',
3173
+ replace: true,
3174
+ scope: { title: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
3175
+ originScope: '&' },
3176
+ templateUrl: 'template/popover/popover-template.html'
3177
+ };
3178
+ })
3179
+
3180
+ .directive( 'popoverTemplate', [ '$tooltip', function ( $tooltip ) {
3181
+ return $tooltip( 'popoverTemplate', 'popover', 'click', {
3182
+ useContentExp: true
3183
+ } );
3184
+ }])
3185
+
2791
3186
  .directive( 'popoverPopup', function () {
2792
3187
  return {
2793
3188
  restrict: 'EA',
2794
3189
  replace: true,
2795
- scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
3190
+ scope: { title: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
2796
3191
  templateUrl: 'template/popover/popover.html'
2797
3192
  };
2798
3193
  })
@@ -2813,7 +3208,7 @@ angular.module('ui.bootstrap.progressbar', [])
2813
3208
  animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
2814
3209
 
2815
3210
  this.bars = [];
2816
- $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max;
3211
+ $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
2817
3212
 
2818
3213
  this.addBar = function(bar, element) {
2819
3214
  if ( !animate ) {
@@ -2857,6 +3252,7 @@ angular.module('ui.bootstrap.progressbar', [])
2857
3252
  require: '^progress',
2858
3253
  scope: {
2859
3254
  value: '=',
3255
+ max: '=?',
2860
3256
  type: '@'
2861
3257
  },
2862
3258
  templateUrl: 'template/progressbar/bar.html',
@@ -2874,6 +3270,7 @@ angular.module('ui.bootstrap.progressbar', [])
2874
3270
  controller: 'ProgressController',
2875
3271
  scope: {
2876
3272
  value: '=',
3273
+ max: '=?',
2877
3274
  type: '@'
2878
3275
  },
2879
3276
  templateUrl: 'template/progressbar/progressbar.html',
@@ -2882,6 +3279,7 @@ angular.module('ui.bootstrap.progressbar', [])
2882
3279
  }
2883
3280
  };
2884
3281
  });
3282
+
2885
3283
  angular.module('ui.bootstrap.rating', [])
2886
3284
 
2887
3285
  .constant('ratingConfig', {
@@ -2897,6 +3295,13 @@ angular.module('ui.bootstrap.rating', [])
2897
3295
  ngModelCtrl = ngModelCtrl_;
2898
3296
  ngModelCtrl.$render = this.render;
2899
3297
 
3298
+ ngModelCtrl.$formatters.push(function(value) {
3299
+ if (angular.isNumber(value) && value << 0 !== value) {
3300
+ value = Math.round(value);
3301
+ }
3302
+ return value;
3303
+ });
3304
+
2900
3305
  this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
2901
3306
  this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
2902
3307
 
@@ -2958,10 +3363,7 @@ angular.module('ui.bootstrap.rating', [])
2958
3363
  replace: true,
2959
3364
  link: function(scope, element, attrs, ctrls) {
2960
3365
  var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2961
-
2962
- if ( ngModelCtrl ) {
2963
- ratingCtrl.init( ngModelCtrl );
2964
- }
3366
+ ratingCtrl.init( ngModelCtrl );
2965
3367
  }
2966
3368
  };
2967
3369
  });
@@ -2995,11 +3397,14 @@ angular.module('ui.bootstrap.tabs', [])
2995
3397
  tabs.push(tab);
2996
3398
  // we can't run the select function on the first tab
2997
3399
  // since that would select it twice
2998
- if (tabs.length === 1) {
3400
+ if (tabs.length === 1 && tab.active !== false) {
2999
3401
  tab.active = true;
3000
3402
  } else if (tab.active) {
3001
3403
  ctrl.select(tab);
3002
3404
  }
3405
+ else {
3406
+ tab.active = false;
3407
+ }
3003
3408
  };
3004
3409
 
3005
3410
  ctrl.removeTab = function removeTab(tab) {
@@ -3146,7 +3551,7 @@ angular.module('ui.bootstrap.tabs', [])
3146
3551
  </file>
3147
3552
  </example>
3148
3553
  */
3149
- .directive('tab', ['$parse', function($parse) {
3554
+ .directive('tab', ['$parse', '$log', function($parse, $log) {
3150
3555
  return {
3151
3556
  require: '^tabset',
3152
3557
  restrict: 'EA',
@@ -3172,7 +3577,18 @@ angular.module('ui.bootstrap.tabs', [])
3172
3577
  });
3173
3578
 
3174
3579
  scope.disabled = false;
3580
+ if ( attrs.disable ) {
3581
+ scope.$parent.$watch($parse(attrs.disable), function(value) {
3582
+ scope.disabled = !! value;
3583
+ });
3584
+ }
3585
+
3586
+ // Deprecation support of "disabled" parameter
3587
+ // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
3588
+ // This code is duplicated from the lines above to make it easy to remove once
3589
+ // the feature has been completely deprecated
3175
3590
  if ( attrs.disabled ) {
3591
+ $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
3176
3592
  scope.$parent.$watch($parse(attrs.disabled), function(value) {
3177
3593
  scope.disabled = !! value;
3178
3594
  });
@@ -3253,7 +3669,8 @@ angular.module('ui.bootstrap.timepicker', [])
3253
3669
  showMeridian: true,
3254
3670
  meridians: null,
3255
3671
  readonlyInput: false,
3256
- mousewheel: true
3672
+ mousewheel: true,
3673
+ arrowkeys: true
3257
3674
  })
3258
3675
 
3259
3676
  .controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) {
@@ -3265,6 +3682,10 @@ angular.module('ui.bootstrap.timepicker', [])
3265
3682
  ngModelCtrl = ngModelCtrl_;
3266
3683
  ngModelCtrl.$render = this.render;
3267
3684
 
3685
+ ngModelCtrl.$formatters.unshift(function (modelValue) {
3686
+ return modelValue ? new Date( modelValue ) : null;
3687
+ });
3688
+
3268
3689
  var hoursInputEl = inputs.eq(0),
3269
3690
  minutesInputEl = inputs.eq(1);
3270
3691
 
@@ -3273,6 +3694,11 @@ angular.module('ui.bootstrap.timepicker', [])
3273
3694
  this.setupMousewheelEvents( hoursInputEl, minutesInputEl );
3274
3695
  }
3275
3696
 
3697
+ var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
3698
+ if (arrowkeys) {
3699
+ this.setupArrowkeyEvents( hoursInputEl, minutesInputEl );
3700
+ }
3701
+
3276
3702
  $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
3277
3703
  this.setupInputEvents( hoursInputEl, minutesInputEl );
3278
3704
  };
@@ -3335,7 +3761,7 @@ angular.module('ui.bootstrap.timepicker', [])
3335
3761
  }
3336
3762
 
3337
3763
  function pad( value ) {
3338
- return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
3764
+ return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value.toString();
3339
3765
  }
3340
3766
 
3341
3767
  // Respond on mousewheel spin
@@ -3361,6 +3787,35 @@ angular.module('ui.bootstrap.timepicker', [])
3361
3787
 
3362
3788
  };
3363
3789
 
3790
+ // Respond on up/down arrowkeys
3791
+ this.setupArrowkeyEvents = function( hoursInputEl, minutesInputEl ) {
3792
+ hoursInputEl.bind('keydown', function(e) {
3793
+ if ( e.which === 38 ) { // up
3794
+ e.preventDefault();
3795
+ $scope.incrementHours();
3796
+ $scope.$apply();
3797
+ }
3798
+ else if ( e.which === 40 ) { // down
3799
+ e.preventDefault();
3800
+ $scope.decrementHours();
3801
+ $scope.$apply();
3802
+ }
3803
+ });
3804
+
3805
+ minutesInputEl.bind('keydown', function(e) {
3806
+ if ( e.which === 38 ) { // up
3807
+ e.preventDefault();
3808
+ $scope.incrementMinutes();
3809
+ $scope.$apply();
3810
+ }
3811
+ else if ( e.which === 40 ) { // down
3812
+ e.preventDefault();
3813
+ $scope.decrementMinutes();
3814
+ $scope.$apply();
3815
+ }
3816
+ });
3817
+ };
3818
+
3364
3819
  this.setupInputEvents = function( hoursInputEl, minutesInputEl ) {
3365
3820
  if ( $scope.readonlyInput ) {
3366
3821
  $scope.updateHours = angular.noop;
@@ -3420,7 +3875,7 @@ angular.module('ui.bootstrap.timepicker', [])
3420
3875
  };
3421
3876
 
3422
3877
  this.render = function() {
3423
- var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null;
3878
+ var date = ngModelCtrl.$viewValue;
3424
3879
 
3425
3880
  if ( isNaN(date) ) {
3426
3881
  ngModelCtrl.$setValidity('time', false);
@@ -3455,7 +3910,9 @@ angular.module('ui.bootstrap.timepicker', [])
3455
3910
  }
3456
3911
 
3457
3912
  $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
3458
- $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
3913
+ if (keyboardChange !== 'm') {
3914
+ $scope.minutes = pad(minutes);
3915
+ }
3459
3916
  $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
3460
3917
  }
3461
3918
 
@@ -3500,6 +3957,96 @@ angular.module('ui.bootstrap.timepicker', [])
3500
3957
  };
3501
3958
  });
3502
3959
 
3960
+ angular.module('ui.bootstrap.transition', [])
3961
+
3962
+ .value('$transitionSuppressDeprecated', false)
3963
+ /**
3964
+ * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
3965
+ * @param {DOMElement} element The DOMElement that will be animated.
3966
+ * @param {string|object|function} trigger The thing that will cause the transition to start:
3967
+ * - As a string, it represents the css class to be added to the element.
3968
+ * - As an object, it represents a hash of style attributes to be applied to the element.
3969
+ * - As a function, it represents a function to be called that will cause the transition to occur.
3970
+ * @return {Promise} A promise that is resolved when the transition finishes.
3971
+ */
3972
+ .factory('$transition', [
3973
+ '$q', '$timeout', '$rootScope', '$log', '$transitionSuppressDeprecated',
3974
+ function($q , $timeout , $rootScope , $log , $transitionSuppressDeprecated) {
3975
+
3976
+ if (!$transitionSuppressDeprecated) {
3977
+ $log.warn('$transition is now deprecated. Use $animate from ngAnimate instead.');
3978
+ }
3979
+
3980
+ var $transition = function(element, trigger, options) {
3981
+ options = options || {};
3982
+ var deferred = $q.defer();
3983
+ var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName'];
3984
+
3985
+ var transitionEndHandler = function(event) {
3986
+ $rootScope.$apply(function() {
3987
+ element.unbind(endEventName, transitionEndHandler);
3988
+ deferred.resolve(element);
3989
+ });
3990
+ };
3991
+
3992
+ if (endEventName) {
3993
+ element.bind(endEventName, transitionEndHandler);
3994
+ }
3995
+
3996
+ // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
3997
+ $timeout(function() {
3998
+ if ( angular.isString(trigger) ) {
3999
+ element.addClass(trigger);
4000
+ } else if ( angular.isFunction(trigger) ) {
4001
+ trigger(element);
4002
+ } else if ( angular.isObject(trigger) ) {
4003
+ element.css(trigger);
4004
+ }
4005
+ //If browser does not support transitions, instantly resolve
4006
+ if ( !endEventName ) {
4007
+ deferred.resolve(element);
4008
+ }
4009
+ });
4010
+
4011
+ // Add our custom cancel function to the promise that is returned
4012
+ // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
4013
+ // i.e. it will therefore never raise a transitionEnd event for that transition
4014
+ deferred.promise.cancel = function() {
4015
+ if ( endEventName ) {
4016
+ element.unbind(endEventName, transitionEndHandler);
4017
+ }
4018
+ deferred.reject('Transition cancelled');
4019
+ };
4020
+
4021
+ return deferred.promise;
4022
+ };
4023
+
4024
+ // Work out the name of the transitionEnd event
4025
+ var transElement = document.createElement('trans');
4026
+ var transitionEndEventNames = {
4027
+ 'WebkitTransition': 'webkitTransitionEnd',
4028
+ 'MozTransition': 'transitionend',
4029
+ 'OTransition': 'oTransitionEnd',
4030
+ 'transition': 'transitionend'
4031
+ };
4032
+ var animationEndEventNames = {
4033
+ 'WebkitTransition': 'webkitAnimationEnd',
4034
+ 'MozTransition': 'animationend',
4035
+ 'OTransition': 'oAnimationEnd',
4036
+ 'transition': 'animationend'
4037
+ };
4038
+ function findEndEventName(endEventNames) {
4039
+ for (var name in endEventNames){
4040
+ if (transElement.style[name] !== undefined) {
4041
+ return endEventNames[name];
4042
+ }
4043
+ }
4044
+ }
4045
+ $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
4046
+ $transition.animationEndEventName = findEndEventName(animationEndEventNames);
4047
+ return $transition;
4048
+ }]);
4049
+
3503
4050
  angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3504
4051
 
3505
4052
  /**
@@ -3545,7 +4092,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3545
4092
  //minimal no of characters that needs to be entered before typeahead kicks-in
3546
4093
  var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
3547
4094
 
3548
- //minimal wait time after last character typed before typehead kicks-in
4095
+ //minimal wait time after last character typed before typeahead kicks-in
3549
4096
  var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
3550
4097
 
3551
4098
  //should it restrict model values to the ones selected from the popup only?
@@ -3633,7 +4180,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3633
4180
  //but we are interested only in responses that correspond to the current view value
3634
4181
  var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
3635
4182
  if (onCurrentRequest && hasFocus) {
3636
- if (matches.length > 0) {
4183
+ if (matches && matches.length > 0) {
3637
4184
 
3638
4185
  scope.activeIdx = focusFirst ? 0 : -1;
3639
4186
  scope.matches.length = 0;
@@ -3674,7 +4221,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3674
4221
  //we need to propagate user's query so we can higlight matches
3675
4222
  scope.query = undefined;
3676
4223
 
3677
- //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
4224
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3678
4225
  var timeoutPromise;
3679
4226
 
3680
4227
  var scheduleSearchWithTimeout = function(inputValue) {
@@ -3727,6 +4274,13 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3727
4274
  var candidateViewValue, emptyViewValue;
3728
4275
  var locals = {};
3729
4276
 
4277
+ // The validity may be set to false via $parsers (see above) if
4278
+ // the model is restricted to selected values. If the model
4279
+ // is set manually it is considered to be valid.
4280
+ if (!isEditable) {
4281
+ modelCtrl.$setValidity('editable', true);
4282
+ }
4283
+
3730
4284
  if (inputFormatter) {
3731
4285
 
3732
4286
  locals.$model = modelValue;
@@ -3754,6 +4308,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3754
4308
  model = parserResult.modelMapper(originalScope, locals);
3755
4309
  $setModelValue(originalScope, model);
3756
4310
  modelCtrl.$setValidity('editable', true);
4311
+ modelCtrl.$setValidity('parse', true);
3757
4312
 
3758
4313
  onSelectCallback(originalScope, {
3759
4314
  $item: item,
@@ -3823,9 +4378,12 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3823
4378
  if (appendToBody) {
3824
4379
  $popup.remove();
3825
4380
  }
4381
+ // Prevent jQuery cache memory leak
4382
+ popUpEl.remove();
3826
4383
  });
3827
4384
 
3828
4385
  var $popup = $compile(popUpEl)(scope);
4386
+
3829
4387
  if (appendToBody) {
3830
4388
  $document.find('body').append($popup);
3831
4389
  } else {
@@ -3871,7 +4429,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3871
4429
  };
3872
4430
  })
3873
4431
 
3874
- .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
4432
+ .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
3875
4433
  return {
3876
4434
  restrict:'EA',
3877
4435
  scope:{
@@ -3881,8 +4439,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3881
4439
  },
3882
4440
  link:function (scope, element, attrs) {
3883
4441
  var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
3884
- $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
3885
- element.replaceWith($compile(tplContent.trim())(scope));
4442
+ $templateRequest(tplUrl).then(function(tplContent) {
4443
+ $compile(tplContent.trim())(scope, function(clonedElement){
4444
+ element.replaceWith(clonedElement);
4445
+ });
3886
4446
  });
3887
4447
  }
3888
4448
  };
@@ -3898,3 +4458,4 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3898
4458
  return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3899
4459
  };
3900
4460
  });
4461
+ !angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>');