angular-ui-bootstrap-rails 0.13.3 → 0.13.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,16 +2,15 @@
2
2
  * angular-ui-bootstrap
3
3
  * http://angular-ui.github.io/bootstrap/
4
4
 
5
- * Version: 0.13.3 - 2015-08-09
5
+ * Version: 0.13.4 - 2015-09-03
6
6
  * License: MIT
7
7
  */
8
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
9
  angular.module('ui.bootstrap.collapse', [])
10
10
 
11
- .directive('collapse', ['$animate', function ($animate) {
12
-
11
+ .directive('collapse', ['$animate', function($animate) {
13
12
  return {
14
- link: function (scope, element, attrs) {
13
+ link: function(scope, element, attrs) {
15
14
  function expand() {
16
15
  element.removeClass('collapse')
17
16
  .addClass('collapsing')
@@ -29,7 +28,7 @@ angular.module('ui.bootstrap.collapse', [])
29
28
  }
30
29
 
31
30
  function collapse() {
32
- if(! element.hasClass('collapse') && ! element.hasClass('in')) {
31
+ if (!element.hasClass('collapse') && !element.hasClass('in')) {
33
32
  return collapseDone();
34
33
  }
35
34
 
@@ -56,7 +55,7 @@ angular.module('ui.bootstrap.collapse', [])
56
55
  element.addClass('collapse');
57
56
  }
58
57
 
59
- scope.$watch(attrs.collapse, function (shouldCollapse) {
58
+ scope.$watch(attrs.collapse, function(shouldCollapse) {
60
59
  if (shouldCollapse) {
61
60
  collapse();
62
61
  } else {
@@ -73,17 +72,17 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
73
72
  closeOthers: true
74
73
  })
75
74
 
76
- .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
77
-
75
+ .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function($scope, $attrs, accordionConfig) {
78
76
  // This array keeps track of the accordion groups
79
77
  this.groups = [];
80
78
 
81
79
  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
82
80
  this.closeOthers = function(openGroup) {
83
- var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
84
- if ( closeOthers ) {
85
- angular.forEach(this.groups, function (group) {
86
- if ( group !== openGroup ) {
81
+ var closeOthers = angular.isDefined($attrs.closeOthers) ?
82
+ $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
83
+ if (closeOthers) {
84
+ angular.forEach(this.groups, function(group) {
85
+ if (group !== openGroup) {
87
86
  group.isOpen = false;
88
87
  }
89
88
  });
@@ -95,7 +94,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
95
94
  var that = this;
96
95
  this.groups.push(groupScope);
97
96
 
98
- groupScope.$on('$destroy', function (event) {
97
+ groupScope.$on('$destroy', function(event) {
99
98
  that.removeGroup(groupScope);
100
99
  });
101
100
  };
@@ -103,7 +102,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
103
102
  // This is called from the accordion-group directive when to remove itself
104
103
  this.removeGroup = function(group) {
105
104
  var index = this.groups.indexOf(group);
106
- if ( index !== -1 ) {
105
+ if (index !== -1) {
107
106
  this.groups.splice(index, 1);
108
107
  }
109
108
  };
@@ -112,7 +111,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
112
111
 
113
112
  // The accordion directive simply sets up the directive controller
114
113
  // and adds an accordion CSS class to itself element.
115
- .directive('accordion', function () {
114
+ .directive('accordion', function() {
116
115
  return {
117
116
  restrict: 'EA',
118
117
  controller: 'AccordionController',
@@ -128,9 +127,9 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
128
127
  // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
129
128
  .directive('accordionGroup', function() {
130
129
  return {
131
- require:'^accordion', // We need this directive to be inside an accordion
132
- restrict:'EA',
133
- transclude:true, // It transcludes the contents of the directive into the template
130
+ require: '^accordion', // We need this directive to be inside an accordion
131
+ restrict: 'EA',
132
+ transclude: true, // It transcludes the contents of the directive into the template
134
133
  replace: true, // The element containing the directive will be replaced with the template
135
134
  templateUrl: function(element, attrs) {
136
135
  return attrs.templateUrl || 'template/accordion/accordion-group.html';
@@ -148,15 +147,20 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
148
147
  link: function(scope, element, attrs, accordionCtrl) {
149
148
  accordionCtrl.addGroup(scope);
150
149
 
150
+ scope.openClass = attrs.openClass || 'panel-open';
151
+ scope.panelClass = attrs.panelClass;
151
152
  scope.$watch('isOpen', function(value) {
152
- if ( value ) {
153
+ element.toggleClass(scope.openClass, value);
154
+ if (value) {
153
155
  accordionCtrl.closeOthers(scope);
154
156
  }
155
157
  });
156
158
 
157
- scope.toggleOpen = function() {
158
- if ( !scope.isDisabled ) {
159
- scope.isOpen = !scope.isOpen;
159
+ scope.toggleOpen = function($event) {
160
+ if (!scope.isDisabled) {
161
+ if (!$event || $event.which === 32) {
162
+ scope.isOpen = !scope.isOpen;
163
+ }
160
164
  }
161
165
  };
162
166
  }
@@ -194,7 +198,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
194
198
  require: '^accordionGroup',
195
199
  link: function(scope, element, attr, controller) {
196
200
  scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
197
- if ( heading ) {
201
+ if (heading) {
198
202
  element.find('span').html('');
199
203
  element.find('span').append(heading);
200
204
  }
@@ -207,14 +211,13 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
207
211
 
208
212
  angular.module('ui.bootstrap.alert', [])
209
213
 
210
- .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
214
+ .controller('AlertController', ['$scope', '$attrs', function($scope, $attrs) {
211
215
  $scope.closeable = !!$attrs.close;
212
216
  this.close = $scope.close;
213
217
  }])
214
218
 
215
- .directive('alert', function () {
219
+ .directive('alert', function() {
216
220
  return {
217
- restrict: 'EA',
218
221
  controller: 'AlertController',
219
222
  controllerAs: 'alert',
220
223
  templateUrl: function(element, attrs) {
@@ -233,7 +236,7 @@ angular.module('ui.bootstrap.alert', [])
233
236
  return {
234
237
  require: 'alert',
235
238
  link: function(scope, element, attrs, alertCtrl) {
236
- $timeout(function(){
239
+ $timeout(function() {
237
240
  alertCtrl.close();
238
241
  }, parseInt(attrs.dismissOnTimeout, 10));
239
242
  }
@@ -267,21 +270,23 @@ angular.module('ui.bootstrap.buttons', [])
267
270
  this.toggleEvent = buttonConfig.toggleEvent || 'click';
268
271
  }])
269
272
 
270
- .directive('btnRadio', function () {
273
+ .directive('btnRadio', function() {
271
274
  return {
272
275
  require: ['btnRadio', 'ngModel'],
273
276
  controller: 'ButtonsController',
274
277
  controllerAs: 'buttons',
275
- link: function (scope, element, attrs, ctrls) {
278
+ link: function(scope, element, attrs, ctrls) {
276
279
  var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
277
280
 
281
+ element.find('input').css({display: 'none'});
282
+
278
283
  //model -> UI
279
- ngModelCtrl.$render = function () {
284
+ ngModelCtrl.$render = function() {
280
285
  element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
281
286
  };
282
287
 
283
288
  //ui->model
284
- element.bind(buttonsCtrl.toggleEvent, function () {
289
+ element.bind(buttonsCtrl.toggleEvent, function() {
285
290
  if (attrs.disabled) {
286
291
  return;
287
292
  }
@@ -289,7 +294,7 @@ angular.module('ui.bootstrap.buttons', [])
289
294
  var isActive = element.hasClass(buttonsCtrl.activeClass);
290
295
 
291
296
  if (!isActive || angular.isDefined(attrs.uncheckable)) {
292
- scope.$apply(function () {
297
+ scope.$apply(function() {
293
298
  ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
294
299
  ngModelCtrl.$render();
295
300
  });
@@ -299,14 +304,16 @@ angular.module('ui.bootstrap.buttons', [])
299
304
  };
300
305
  })
301
306
 
302
- .directive('btnCheckbox', function () {
307
+ .directive('btnCheckbox', ['$document', function($document) {
303
308
  return {
304
309
  require: ['btnCheckbox', 'ngModel'],
305
310
  controller: 'ButtonsController',
306
311
  controllerAs: 'button',
307
- link: function (scope, element, attrs, ctrls) {
312
+ link: function(scope, element, attrs, ctrls) {
308
313
  var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
309
314
 
315
+ element.find('input').css({display: 'none'});
316
+
310
317
  function getTrueValue() {
311
318
  return getCheckboxValue(attrs.btnCheckboxTrue, true);
312
319
  }
@@ -321,24 +328,36 @@ angular.module('ui.bootstrap.buttons', [])
321
328
  }
322
329
 
323
330
  //model -> UI
324
- ngModelCtrl.$render = function () {
331
+ ngModelCtrl.$render = function() {
325
332
  element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
326
333
  };
327
334
 
328
335
  //ui->model
329
- element.bind(buttonsCtrl.toggleEvent, function () {
336
+ element.bind(buttonsCtrl.toggleEvent, function() {
330
337
  if (attrs.disabled) {
331
338
  return;
332
339
  }
333
340
 
334
- scope.$apply(function () {
341
+ scope.$apply(function() {
342
+ ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
343
+ ngModelCtrl.$render();
344
+ });
345
+ });
346
+
347
+ //accessibility
348
+ element.on('keypress', function(e) {
349
+ if (attrs.disabled || e.which !== 32 || $document[0].activeElement !== element[0]) {
350
+ return;
351
+ }
352
+
353
+ scope.$apply(function() {
335
354
  ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
336
355
  ngModelCtrl.$render();
337
356
  });
338
357
  });
339
358
  }
340
359
  };
341
- });
360
+ }]);
342
361
 
343
362
  /**
344
363
  * @ngdoc overview
@@ -534,7 +553,7 @@ angular.module('ui.bootstrap.carousel', [])
534
553
  } else if (currentIndex > index) {
535
554
  currentIndex--;
536
555
  }
537
-
556
+
538
557
  //clean the currentSlide when no more slide
539
558
  if (slides.length === 0) {
540
559
  self.currentSlide = null;
@@ -658,6 +677,7 @@ function CarouselDemoCtrl($scope) {
658
677
  },
659
678
  scope: {
660
679
  active: '=?',
680
+ actual: '=?',
661
681
  index: '=?'
662
682
  },
663
683
  link: function (scope, element, attrs, carouselCtrl) {
@@ -822,6 +842,10 @@ angular.module('ui.bootstrap.dateparser', [])
822
842
  regex: '1?[0-9]|2[0-3]',
823
843
  apply: function(value) { this.hours = +value; }
824
844
  },
845
+ 'h': {
846
+ regex: '[0-9]|1[0-2]',
847
+ apply: function(value) { this.hours = +value; }
848
+ },
825
849
  'mm': {
826
850
  regex: '[0-5][0-9]',
827
851
  apply: function(value) { this.minutes = +value; }
@@ -884,14 +908,14 @@ angular.module('ui.bootstrap.dateparser', [])
884
908
  }
885
909
 
886
910
  this.parse = function(input, format, baseDate) {
887
- if ( !angular.isString(input) || !format ) {
911
+ if (!angular.isString(input) || !format) {
888
912
  return input;
889
913
  }
890
914
 
891
915
  format = $locale.DATETIME_FORMATS[format] || format;
892
916
  format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
893
917
 
894
- if ( !this.parsers[format] ) {
918
+ if (!this.parsers[format]) {
895
919
  this.parsers[format] = createParser(format);
896
920
  }
897
921
 
@@ -900,7 +924,7 @@ angular.module('ui.bootstrap.dateparser', [])
900
924
  map = parser.map,
901
925
  results = input.match(regex);
902
926
 
903
- if ( results && results.length ) {
927
+ if (results && results.length) {
904
928
  var fields, dt;
905
929
  if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
906
930
  fields = {
@@ -919,15 +943,16 @@ angular.module('ui.bootstrap.dateparser', [])
919
943
  fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
920
944
  }
921
945
 
922
- for( var i = 1, n = results.length; i < n; i++ ) {
946
+ for (var i = 1, n = results.length; i < n; i++) {
923
947
  var mapper = map[i-1];
924
- if ( mapper.apply ) {
948
+ if (mapper.apply) {
925
949
  mapper.apply.call(fields, results[i]);
926
950
  }
927
951
  }
928
952
 
929
- if ( isValid(fields.year, fields.month, fields.date) ) {
930
- dt = new Date(fields.year, fields.month, fields.date, fields.hours, fields.minutes, fields.seconds,
953
+ if (isValid(fields.year, fields.month, fields.date)) {
954
+ dt = new Date(fields.year, fields.month, fields.date,
955
+ fields.hours, fields.minutes, fields.seconds,
931
956
  fields.milliseconds || 0);
932
957
  }
933
958
 
@@ -942,12 +967,12 @@ angular.module('ui.bootstrap.dateparser', [])
942
967
  return false;
943
968
  }
944
969
 
945
- if ( month === 1 && date > 28) {
946
- return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
970
+ if (month === 1 && date > 28) {
971
+ return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
947
972
  }
948
973
 
949
- if ( month === 3 || month === 5 || month === 8 || month === 10) {
950
- return date < 31;
974
+ if (month === 3 || month === 5 || month === 8 || month === 10) {
975
+ return date < 31;
951
976
  }
952
977
 
953
978
  return true;
@@ -962,8 +987,7 @@ angular.module('ui.bootstrap.position', [])
962
987
  * relation to other, existing elements (this is the case for tooltips, popovers,
963
988
  * typeahead suggestions etc.).
964
989
  */
965
- .factory('$position', ['$document', '$window', function ($document, $window) {
966
-
990
+ .factory('$position', ['$document', '$window', function($document, $window) {
967
991
  function getStyle(el, cssprop) {
968
992
  if (el.currentStyle) { //IE
969
993
  return el.currentStyle[cssprop];
@@ -986,7 +1010,7 @@ angular.module('ui.bootstrap.position', [])
986
1010
  * returns the closest, non-statically positioned parentOffset of a given element
987
1011
  * @param element
988
1012
  */
989
- var parentOffsetEl = function (element) {
1013
+ var parentOffsetEl = function(element) {
990
1014
  var docDomEl = $document[0];
991
1015
  var offsetParent = element.offsetParent || docDomEl;
992
1016
  while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
@@ -1000,7 +1024,7 @@ angular.module('ui.bootstrap.position', [])
1000
1024
  * Provides read-only equivalent of jQuery's position function:
1001
1025
  * http://api.jquery.com/position/
1002
1026
  */
1003
- position: function (element) {
1027
+ position: function(element) {
1004
1028
  var elBCR = this.offset(element);
1005
1029
  var offsetParentBCR = { top: 0, left: 0 };
1006
1030
  var offsetParentEl = parentOffsetEl(element[0]);
@@ -1023,7 +1047,7 @@ angular.module('ui.bootstrap.position', [])
1023
1047
  * Provides read-only equivalent of jQuery's offset function:
1024
1048
  * http://api.jquery.com/offset/
1025
1049
  */
1026
- offset: function (element) {
1050
+ offset: function(element) {
1027
1051
  var boundingClientRect = element[0].getBoundingClientRect();
1028
1052
  return {
1029
1053
  width: boundingClientRect.width || element.prop('offsetWidth'),
@@ -1036,8 +1060,7 @@ angular.module('ui.bootstrap.position', [])
1036
1060
  /**
1037
1061
  * Provides coordinates for the targetEl in relation to hostEl
1038
1062
  */
1039
- positionElements: function (hostEl, targetEl, positionStr, appendToBody) {
1040
-
1063
+ positionElements: function(hostEl, targetEl, positionStr, appendToBody) {
1041
1064
  var positionStrParts = positionStr.split('-');
1042
1065
  var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
1043
1066
 
@@ -1052,25 +1075,25 @@ angular.module('ui.bootstrap.position', [])
1052
1075
  targetElHeight = targetEl.prop('offsetHeight');
1053
1076
 
1054
1077
  var shiftWidth = {
1055
- center: function () {
1078
+ center: function() {
1056
1079
  return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
1057
1080
  },
1058
- left: function () {
1081
+ left: function() {
1059
1082
  return hostElPos.left;
1060
1083
  },
1061
- right: function () {
1084
+ right: function() {
1062
1085
  return hostElPos.left + hostElPos.width;
1063
1086
  }
1064
1087
  };
1065
1088
 
1066
1089
  var shiftHeight = {
1067
- center: function () {
1090
+ center: function() {
1068
1091
  return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
1069
1092
  },
1070
- top: function () {
1093
+ top: function() {
1071
1094
  return hostElPos.top;
1072
1095
  },
1073
- bottom: function () {
1096
+ bottom: function() {
1074
1097
  return hostElPos.top + hostElPos.height;
1075
1098
  }
1076
1099
  };
@@ -1138,13 +1161,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1138
1161
 
1139
1162
  // Configuration attributes
1140
1163
  angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
1141
- 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function( key, index ) {
1164
+ 'showWeeks', 'startingDay', 'yearRange', 'shortcutPropagation'], function(key, index) {
1142
1165
  self[key] = angular.isDefined($attrs[key]) ? (index < 6 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
1143
1166
  });
1144
1167
 
1145
1168
  // Watchable date attributes
1146
- angular.forEach(['minDate', 'maxDate'], function( key ) {
1147
- if ( $attrs[key] ) {
1169
+ angular.forEach(['minDate', 'maxDate'], function(key) {
1170
+ if ($attrs[key]) {
1148
1171
  $scope.$parent.$watch($parse($attrs[key]), function(value) {
1149
1172
  self[key] = value ? new Date(value) : null;
1150
1173
  self.refreshView();
@@ -1154,12 +1177,12 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1154
1177
  }
1155
1178
  });
1156
1179
 
1157
- angular.forEach(['minMode', 'maxMode'], function( key ) {
1158
- if ( $attrs[key] ) {
1180
+ angular.forEach(['minMode', 'maxMode'], function(key) {
1181
+ if ($attrs[key]) {
1159
1182
  $scope.$parent.$watch($parse($attrs[key]), function(value) {
1160
1183
  self[key] = angular.isDefined(value) ? value : $attrs[key];
1161
1184
  $scope[key] = self[key];
1162
- if ((key == 'minMode' && self.modes.indexOf( $scope.datepickerMode ) < self.modes.indexOf( self[key] )) || (key == 'maxMode' && self.modes.indexOf( $scope.datepickerMode ) > self.modes.indexOf( self[key] ))) {
1185
+ if ((key == 'minMode' && self.modes.indexOf($scope.datepickerMode) < self.modes.indexOf(self[key])) || (key == 'maxMode' && self.modes.indexOf($scope.datepickerMode) > self.modes.indexOf(self[key]))) {
1163
1186
  $scope.datepickerMode = self[key];
1164
1187
  }
1165
1188
  });
@@ -1172,16 +1195,16 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1172
1195
  $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
1173
1196
  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1174
1197
 
1175
- if(angular.isDefined($attrs.initDate)) {
1198
+ if (angular.isDefined($attrs.initDate)) {
1176
1199
  this.activeDate = $scope.$parent.$eval($attrs.initDate) || new Date();
1177
- $scope.$parent.$watch($attrs.initDate, function(initDate){
1178
- if(initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)){
1200
+ $scope.$parent.$watch($attrs.initDate, function(initDate) {
1201
+ if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
1179
1202
  self.activeDate = initDate;
1180
1203
  self.refreshView();
1181
1204
  }
1182
1205
  });
1183
1206
  } else {
1184
- this.activeDate = new Date();
1207
+ this.activeDate = new Date();
1185
1208
  }
1186
1209
 
1187
1210
  $scope.isActive = function(dateObject) {
@@ -1192,7 +1215,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1192
1215
  return false;
1193
1216
  };
1194
1217
 
1195
- this.init = function( ngModelCtrl_ ) {
1218
+ this.init = function(ngModelCtrl_) {
1196
1219
  ngModelCtrl = ngModelCtrl_;
1197
1220
 
1198
1221
  ngModelCtrl.$render = function() {
@@ -1201,13 +1224,13 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1201
1224
  };
1202
1225
 
1203
1226
  this.render = function() {
1204
- if ( ngModelCtrl.$viewValue ) {
1205
- var date = new Date( ngModelCtrl.$viewValue ),
1227
+ if (ngModelCtrl.$viewValue) {
1228
+ var date = new Date(ngModelCtrl.$viewValue),
1206
1229
  isValid = !isNaN(date);
1207
1230
 
1208
- if ( isValid ) {
1231
+ if (isValid) {
1209
1232
  this.activeDate = date;
1210
- } else if ( !$datepickerSuppressError ) {
1233
+ } else if (!$datepickerSuppressError) {
1211
1234
  $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
1212
1235
  }
1213
1236
  }
@@ -1215,7 +1238,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1215
1238
  };
1216
1239
 
1217
1240
  this.refreshView = function() {
1218
- if ( this.element ) {
1241
+ if (this.element) {
1219
1242
  this._refreshView();
1220
1243
 
1221
1244
  var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
@@ -1235,11 +1258,11 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1235
1258
  };
1236
1259
  };
1237
1260
 
1238
- this.isDisabled = function( date ) {
1261
+ this.isDisabled = function(date) {
1239
1262
  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})));
1240
1263
  };
1241
1264
 
1242
- this.customClass = function( date ) {
1265
+ this.customClass = function(date) {
1243
1266
  return $scope.customClass({date: date, mode: $scope.datepickerMode});
1244
1267
  };
1245
1268
 
@@ -1263,37 +1286,37 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1263
1286
  date.setHours(hours === 23 ? hours + 2 : 0);
1264
1287
  };
1265
1288
 
1266
- $scope.select = function( date ) {
1267
- if ( $scope.datepickerMode === self.minMode ) {
1268
- var dt = ngModelCtrl.$viewValue ? new Date( ngModelCtrl.$viewValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1269
- dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1270
- ngModelCtrl.$setViewValue( dt );
1289
+ $scope.select = function(date) {
1290
+ if ($scope.datepickerMode === self.minMode) {
1291
+ var dt = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : new Date(0, 0, 0, 0, 0, 0, 0);
1292
+ dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
1293
+ ngModelCtrl.$setViewValue(dt);
1271
1294
  ngModelCtrl.$render();
1272
1295
  } else {
1273
1296
  self.activeDate = date;
1274
- $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ];
1297
+ $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) - 1];
1275
1298
  }
1276
1299
  };
1277
1300
 
1278
- $scope.move = function( direction ) {
1301
+ $scope.move = function(direction) {
1279
1302
  var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1280
1303
  month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1281
1304
  self.activeDate.setFullYear(year, month, 1);
1282
1305
  self.refreshView();
1283
1306
  };
1284
1307
 
1285
- $scope.toggleMode = function( direction ) {
1308
+ $scope.toggleMode = function(direction) {
1286
1309
  direction = direction || 1;
1287
1310
 
1288
1311
  if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
1289
1312
  return;
1290
1313
  }
1291
1314
 
1292
- $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ];
1315
+ $scope.datepickerMode = self.modes[self.modes.indexOf($scope.datepickerMode) + direction];
1293
1316
  };
1294
1317
 
1295
1318
  // Key event mapper
1296
- $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' };
1319
+ $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
1297
1320
 
1298
1321
  var focusElement = function() {
1299
1322
  self.element[0].focus();
@@ -1302,20 +1325,20 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1302
1325
  // Listen for focus requests from popup directive
1303
1326
  $scope.$on('datepicker.focus', focusElement);
1304
1327
 
1305
- $scope.keydown = function( evt ) {
1328
+ $scope.keydown = function(evt) {
1306
1329
  var key = $scope.keys[evt.which];
1307
1330
 
1308
- if ( !key || evt.shiftKey || evt.altKey ) {
1331
+ if (!key || evt.shiftKey || evt.altKey) {
1309
1332
  return;
1310
1333
  }
1311
1334
 
1312
1335
  evt.preventDefault();
1313
- if(!self.shortcutPropagation){
1314
- evt.stopPropagation();
1336
+ if (!self.shortcutPropagation) {
1337
+ evt.stopPropagation();
1315
1338
  }
1316
1339
 
1317
1340
  if (key === 'enter' || key === 'space') {
1318
- if ( self.isDisabled(self.activeDate)) {
1341
+ if (self.isDisabled(self.activeDate)) {
1319
1342
  return; // do nothing
1320
1343
  }
1321
1344
  $scope.select(self.activeDate);
@@ -1330,7 +1353,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1330
1353
  };
1331
1354
  }])
1332
1355
 
1333
- .directive( 'datepicker', function () {
1356
+ .directive('datepicker', function() {
1334
1357
  return {
1335
1358
  restrict: 'EA',
1336
1359
  replace: true,
@@ -1354,7 +1377,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1354
1377
  };
1355
1378
  })
1356
1379
 
1357
- .directive('daypicker', ['dateFilter', function (dateFilter) {
1380
+ .directive('daypicker', ['dateFilter', function(dateFilter) {
1358
1381
  return {
1359
1382
  restrict: 'EA',
1360
1383
  replace: true,
@@ -1367,17 +1390,17 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1367
1390
  ctrl.element = element;
1368
1391
 
1369
1392
  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1370
- function getDaysInMonth( year, month ) {
1393
+ function getDaysInMonth(year, month) {
1371
1394
  return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
1372
1395
  }
1373
1396
 
1374
1397
  function getDates(startDate, n) {
1375
1398
  var dates = new Array(n), current = new Date(startDate), i = 0, date;
1376
- while ( i < n ) {
1399
+ while (i < n) {
1377
1400
  date = new Date(current);
1378
1401
  ctrl.fixTimeZone(date);
1379
1402
  dates[i++] = date;
1380
- current.setDate( current.getDate() + 1 );
1403
+ current.setDate(current.getDate() + 1);
1381
1404
  }
1382
1405
  return dates;
1383
1406
  }
@@ -1390,8 +1413,8 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1390
1413
  numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
1391
1414
  firstDate = new Date(firstDayOfMonth);
1392
1415
 
1393
- if ( numDisplayedFromPreviousMonth > 0 ) {
1394
- firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
1416
+ if (numDisplayedFromPreviousMonth > 0) {
1417
+ firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1395
1418
  }
1396
1419
 
1397
1420
  // 42 is the number of days on a six-month calendar
@@ -1414,19 +1437,19 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1414
1437
  scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
1415
1438
  scope.rows = ctrl.split(days, 7);
1416
1439
 
1417
- if ( scope.showWeeks ) {
1440
+ if (scope.showWeeks) {
1418
1441
  scope.weekNumbers = [];
1419
1442
  var thursdayIndex = (4 + 7 - ctrl.startingDay) % 7,
1420
1443
  numWeeks = scope.rows.length;
1421
1444
  for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1422
1445
  scope.weekNumbers.push(
1423
- getISO8601WeekNumber( scope.rows[curWeek][thursdayIndex].date ));
1446
+ getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
1424
1447
  }
1425
1448
  }
1426
1449
  };
1427
1450
 
1428
1451
  ctrl.compare = function(date1, date2) {
1429
- return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
1452
+ return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
1430
1453
  };
1431
1454
 
1432
1455
  function getISO8601WeekNumber(date) {
@@ -1438,7 +1461,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1438
1461
  return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1439
1462
  }
1440
1463
 
1441
- ctrl.handleKeyDown = function( key, evt ) {
1464
+ ctrl.handleKeyDown = function(key, evt) {
1442
1465
  var date = ctrl.activeDate.getDate();
1443
1466
 
1444
1467
  if (key === 'left') {
@@ -1466,7 +1489,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1466
1489
  };
1467
1490
  }])
1468
1491
 
1469
- .directive('monthpicker', ['dateFilter', function (dateFilter) {
1492
+ .directive('monthpicker', ['dateFilter', function(dateFilter) {
1470
1493
  return {
1471
1494
  restrict: 'EA',
1472
1495
  replace: true,
@@ -1481,7 +1504,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1481
1504
  year = ctrl.activeDate.getFullYear(),
1482
1505
  date;
1483
1506
 
1484
- for ( var i = 0; i < 12; i++ ) {
1507
+ for (var i = 0; i < 12; i++) {
1485
1508
  date = new Date(year, i, 1);
1486
1509
  ctrl.fixTimeZone(date);
1487
1510
  months[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatMonth), {
@@ -1494,10 +1517,10 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1494
1517
  };
1495
1518
 
1496
1519
  ctrl.compare = function(date1, date2) {
1497
- return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
1520
+ return new Date(date1.getFullYear(), date1.getMonth()) - new Date(date2.getFullYear(), date2.getMonth());
1498
1521
  };
1499
1522
 
1500
- ctrl.handleKeyDown = function( key, evt ) {
1523
+ ctrl.handleKeyDown = function(key, evt) {
1501
1524
  var date = ctrl.activeDate.getMonth();
1502
1525
 
1503
1526
  if (key === 'left') {
@@ -1524,7 +1547,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1524
1547
  };
1525
1548
  }])
1526
1549
 
1527
- .directive('yearpicker', ['dateFilter', function (dateFilter) {
1550
+ .directive('yearpicker', ['dateFilter', function(dateFilter) {
1528
1551
  return {
1529
1552
  restrict: 'EA',
1530
1553
  replace: true,
@@ -1543,7 +1566,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1543
1566
  ctrl._refreshView = function() {
1544
1567
  var years = new Array(range), date;
1545
1568
 
1546
- for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) {
1569
+ for (var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++) {
1547
1570
  date = new Date(start + i, 0, 1);
1548
1571
  ctrl.fixTimeZone(date);
1549
1572
  years[i] = angular.extend(ctrl.createDateObject(date, ctrl.formatYear), {
@@ -1559,7 +1582,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1559
1582
  return date1.getFullYear() - date2.getFullYear();
1560
1583
  };
1561
1584
 
1562
- ctrl.handleKeyDown = function( key, evt ) {
1585
+ ctrl.handleKeyDown = function(key, evt) {
1563
1586
  var date = ctrl.activeDate.getFullYear();
1564
1587
 
1565
1588
  if (key === 'left') {
@@ -1573,9 +1596,9 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1573
1596
  } else if (key === 'pageup' || key === 'pagedown') {
1574
1597
  date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years;
1575
1598
  } else if (key === 'home') {
1576
- date = getStartingYear( ctrl.activeDate.getFullYear() );
1599
+ date = getStartingYear(ctrl.activeDate.getFullYear());
1577
1600
  } else if (key === 'end') {
1578
- date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1;
1601
+ date = getStartingYear(ctrl.activeDate.getFullYear()) + range - 1;
1579
1602
  }
1580
1603
  ctrl.activeDate.setFullYear(date);
1581
1604
  };
@@ -1604,7 +1627,7 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootst
1604
1627
  })
1605
1628
 
1606
1629
  .directive('datepickerPopup', ['$compile', '$parse', '$document', '$rootScope', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig', '$timeout',
1607
- function ($compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
1630
+ function($compile, $parse, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout) {
1608
1631
  return {
1609
1632
  restrict: 'EA',
1610
1633
  require: 'ngModel',
@@ -1622,14 +1645,28 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1622
1645
  appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody,
1623
1646
  onOpenFocus = angular.isDefined(attrs.onOpenFocus) ? scope.$parent.$eval(attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus,
1624
1647
  datepickerPopupTemplateUrl = angular.isDefined(attrs.datepickerPopupTemplateUrl) ? attrs.datepickerPopupTemplateUrl : datepickerPopupConfig.datepickerPopupTemplateUrl,
1625
- datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
1648
+ datepickerTemplateUrl = angular.isDefined(attrs.datepickerTemplateUrl) ? attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl,
1649
+ cache = {};
1626
1650
 
1627
1651
  scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
1628
1652
 
1629
- scope.getText = function( key ) {
1653
+ scope.getText = function(key) {
1630
1654
  return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
1631
1655
  };
1632
1656
 
1657
+ scope.isDisabled = function(date) {
1658
+ if (date === 'today') {
1659
+ date = new Date();
1660
+ }
1661
+
1662
+ return ((scope.watchData.minDate && scope.compare(date, cache.minDate) < 0) ||
1663
+ (scope.watchData.maxDate && scope.compare(date, cache.maxDate) > 0));
1664
+ };
1665
+
1666
+ scope.compare = function(date1, date2) {
1667
+ return (new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()));
1668
+ };
1669
+
1633
1670
  var isHtml5DateInput = false;
1634
1671
  if (datepickerPopupConfig.html5Types[attrs.type]) {
1635
1672
  dateFormat = datepickerPopupConfig.html5Types[attrs.type];
@@ -1667,7 +1704,7 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1667
1704
  'template-url': datepickerPopupTemplateUrl
1668
1705
  });
1669
1706
 
1670
- function cameltoDash( string ){
1707
+ function cameltoDash(string) {
1671
1708
  return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
1672
1709
  }
1673
1710
 
@@ -1676,38 +1713,41 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1676
1713
  datepickerEl.attr('template-url', datepickerTemplateUrl);
1677
1714
 
1678
1715
  if (isHtml5DateInput) {
1679
- if (attrs.type == 'month') {
1716
+ if (attrs.type === 'month') {
1680
1717
  datepickerEl.attr('datepicker-mode', '"month"');
1681
1718
  datepickerEl.attr('min-mode', 'month');
1682
1719
  }
1683
1720
  }
1684
1721
 
1685
- if ( attrs.datepickerOptions ) {
1722
+ if (attrs.datepickerOptions) {
1686
1723
  var options = scope.$parent.$eval(attrs.datepickerOptions);
1687
- if(options && options.initDate) {
1724
+ if (options && options.initDate) {
1688
1725
  scope.initDate = options.initDate;
1689
- datepickerEl.attr( 'init-date', 'initDate' );
1726
+ datepickerEl.attr('init-date', 'initDate');
1690
1727
  delete options.initDate;
1691
1728
  }
1692
- angular.forEach(options, function( value, option ) {
1729
+ angular.forEach(options, function(value, option) {
1693
1730
  datepickerEl.attr( cameltoDash(option), value );
1694
1731
  });
1695
1732
  }
1696
1733
 
1697
1734
  scope.watchData = {};
1698
- angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function( key ) {
1699
- if ( attrs[key] ) {
1735
+ angular.forEach(['minMode', 'maxMode', 'minDate', 'maxDate', 'datepickerMode', 'initDate', 'shortcutPropagation'], function(key) {
1736
+ if (attrs[key]) {
1700
1737
  var getAttribute = $parse(attrs[key]);
1701
- scope.$parent.$watch(getAttribute, function(value){
1738
+ scope.$parent.$watch(getAttribute, function(value) {
1702
1739
  scope.watchData[key] = value;
1740
+ if (key === 'minDate' || key === 'maxDate') {
1741
+ cache[key] = new Date(value);
1742
+ }
1703
1743
  });
1704
1744
  datepickerEl.attr(cameltoDash(key), 'watchData.' + key);
1705
1745
 
1706
1746
  // Propagate changes from datepicker to outside
1707
- if ( key === 'datepickerMode' ) {
1747
+ if (key === 'datepickerMode') {
1708
1748
  var setAttribute = getAttribute.assign;
1709
1749
  scope.$watch('watchData.' + key, function(value, oldvalue) {
1710
- if ( angular.isFunction(setAttribute) && value !== oldvalue ) {
1750
+ if (angular.isFunction(setAttribute) && value !== oldvalue) {
1711
1751
  setAttribute(scope.$parent, value);
1712
1752
  }
1713
1753
  });
@@ -1722,7 +1762,7 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1722
1762
  datepickerEl.attr('show-weeks', attrs.showWeeks);
1723
1763
  }
1724
1764
 
1725
- if (attrs.customClass){
1765
+ if (attrs.customClass) {
1726
1766
  datepickerEl.attr('custom-class', 'customClass({ date: date, mode: mode })');
1727
1767
  }
1728
1768
 
@@ -1775,13 +1815,12 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1775
1815
  ngModel.$$parserName = 'date';
1776
1816
  ngModel.$validators.date = validator;
1777
1817
  ngModel.$parsers.unshift(parseDate);
1778
- ngModel.$formatters.push(function (value) {
1818
+ ngModel.$formatters.push(function(value) {
1779
1819
  scope.date = value;
1780
1820
  return ngModel.$isEmpty(value) ? value : dateFilter(value, dateFormat);
1781
1821
  });
1782
- }
1783
- else {
1784
- ngModel.$formatters.push(function (value) {
1822
+ } else {
1823
+ ngModel.$formatters.push(function(value) {
1785
1824
  scope.date = value;
1786
1825
  return value;
1787
1826
  });
@@ -1796,19 +1835,19 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1796
1835
  element.val(date);
1797
1836
  ngModel.$setViewValue(date);
1798
1837
 
1799
- if ( closeOnDateSelection ) {
1838
+ if (closeOnDateSelection) {
1800
1839
  scope.isOpen = false;
1801
1840
  element[0].focus();
1802
1841
  }
1803
1842
  };
1804
1843
 
1805
1844
  // Detect changes in the view from the text box
1806
- ngModel.$viewChangeListeners.push(function () {
1845
+ ngModel.$viewChangeListeners.push(function() {
1807
1846
  scope.date = dateParser.parse(ngModel.$viewValue, dateFormat, scope.date);
1808
1847
  });
1809
1848
 
1810
1849
  var documentClickBind = function(event) {
1811
- if (scope.isOpen && !element[0].contains(event.target)) {
1850
+ if (scope.isOpen && !(element[0].contains(event.target) || popupEl[0].contains(event.target))) {
1812
1851
  scope.$apply(function() {
1813
1852
  scope.isOpen = false;
1814
1853
  });
@@ -1856,7 +1895,7 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1856
1895
  }
1857
1896
  });
1858
1897
 
1859
- scope.select = function( date ) {
1898
+ scope.select = function(date) {
1860
1899
  if (date === 'today') {
1861
1900
  var today = new Date();
1862
1901
  if (angular.isDate(scope.date)) {
@@ -1866,7 +1905,7 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1866
1905
  date = new Date(today.setHours(0, 0, 0, 0));
1867
1906
  }
1868
1907
  }
1869
- scope.dateSelection( date );
1908
+ scope.dateSelection(date);
1870
1909
  };
1871
1910
 
1872
1911
  scope.close = function() {
@@ -1878,7 +1917,7 @@ function ($compile, $parse, $document, $rootScope, $position, dateFilter, datePa
1878
1917
  // Prevent jQuery cache memory leak (template is now redundant after linking)
1879
1918
  popupEl.remove();
1880
1919
 
1881
- if ( appendToBody ) {
1920
+ if (appendToBody) {
1882
1921
  $document.find('body').append($popup);
1883
1922
  } else {
1884
1923
  element.after($popup);
@@ -1921,36 +1960,36 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1921
1960
  .service('dropdownService', ['$document', '$rootScope', function($document, $rootScope) {
1922
1961
  var openScope = null;
1923
1962
 
1924
- this.open = function( dropdownScope ) {
1925
- if ( !openScope ) {
1963
+ this.open = function(dropdownScope) {
1964
+ if (!openScope) {
1926
1965
  $document.bind('click', closeDropdown);
1927
1966
  $document.bind('keydown', keybindFilter);
1928
1967
  }
1929
1968
 
1930
- if ( openScope && openScope !== dropdownScope ) {
1969
+ if (openScope && openScope !== dropdownScope) {
1931
1970
  openScope.isOpen = false;
1932
1971
  }
1933
1972
 
1934
1973
  openScope = dropdownScope;
1935
1974
  };
1936
1975
 
1937
- this.close = function( dropdownScope ) {
1938
- if ( openScope === dropdownScope ) {
1976
+ this.close = function(dropdownScope) {
1977
+ if (openScope === dropdownScope) {
1939
1978
  openScope = null;
1940
1979
  $document.unbind('click', closeDropdown);
1941
1980
  $document.unbind('keydown', keybindFilter);
1942
1981
  }
1943
1982
  };
1944
1983
 
1945
- var closeDropdown = function( evt ) {
1984
+ var closeDropdown = function(evt) {
1946
1985
  // This method may still be called during the same mouse event that
1947
1986
  // unbound this event handler. So check openScope before proceeding.
1948
1987
  if (!openScope) { return; }
1949
1988
 
1950
- if( evt && openScope.getAutoClose() === 'disabled' ) { return ; }
1989
+ if (evt && openScope.getAutoClose() === 'disabled') { return ; }
1951
1990
 
1952
1991
  var toggleElement = openScope.getToggleElement();
1953
- if ( evt && toggleElement && toggleElement[0].contains(evt.target) ) {
1992
+ if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
1954
1993
  return;
1955
1994
  }
1956
1995
 
@@ -1967,12 +2006,11 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1967
2006
  }
1968
2007
  };
1969
2008
 
1970
- var keybindFilter = function( evt ) {
1971
- if ( evt.which === 27 ) {
2009
+ var keybindFilter = function(evt) {
2010
+ if (evt.which === 27) {
1972
2011
  openScope.focusToggleElement();
1973
2012
  closeDropdown();
1974
- }
1975
- else if ( openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen ) {
2013
+ } else if (openScope.isKeynavEnabled() && /(38|40)/.test(evt.which) && openScope.isOpen) {
1976
2014
  evt.preventDefault();
1977
2015
  evt.stopPropagation();
1978
2016
  openScope.focusDropdownEntry(evt.which);
@@ -1983,19 +2021,20 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
1983
2021
  .controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', '$position', '$document', '$compile', '$templateRequest', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate, $position, $document, $compile, $templateRequest) {
1984
2022
  var self = this,
1985
2023
  scope = $scope.$new(), // create a child scope so we are not polluting original one
1986
- templateScope,
2024
+ templateScope,
1987
2025
  openClass = dropdownConfig.openClass,
1988
2026
  getIsOpen,
1989
2027
  setIsOpen = angular.noop,
1990
2028
  toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
1991
2029
  appendToBody = false,
1992
- keynavEnabled =false,
1993
- selectedOption = null;
2030
+ keynavEnabled = false,
2031
+ selectedOption = null,
2032
+ body = $document.find('body');
1994
2033
 
1995
- this.init = function( element ) {
2034
+ this.init = function(element) {
1996
2035
  self.$element = element;
1997
2036
 
1998
- if ( $attrs.isOpen ) {
2037
+ if ($attrs.isOpen) {
1999
2038
  getIsOpen = $parse($attrs.isOpen);
2000
2039
  setIsOpen = getIsOpen.assign;
2001
2040
 
@@ -2007,15 +2046,16 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2007
2046
  appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
2008
2047
  keynavEnabled = angular.isDefined($attrs.keyboardNav);
2009
2048
 
2010
- if ( appendToBody && self.dropdownMenu ) {
2011
- $document.find('body').append( self.dropdownMenu );
2049
+ if (appendToBody && self.dropdownMenu) {
2050
+ body.append(self.dropdownMenu);
2051
+ body.addClass('dropdown');
2012
2052
  element.on('$destroy', function handleDestroyEvent() {
2013
2053
  self.dropdownMenu.remove();
2014
2054
  });
2015
2055
  }
2016
2056
  };
2017
2057
 
2018
- this.toggle = function( open ) {
2058
+ this.toggle = function(open) {
2019
2059
  return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
2020
2060
  };
2021
2061
 
@@ -2047,7 +2087,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2047
2087
 
2048
2088
  switch (keyCode) {
2049
2089
  case (40): {
2050
- if ( !angular.isNumber(self.selectedOption)) {
2090
+ if (!angular.isNumber(self.selectedOption)) {
2051
2091
  self.selectedOption = 0;
2052
2092
  } else {
2053
2093
  self.selectedOption = (self.selectedOption === elems.length -1 ?
@@ -2057,12 +2097,11 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2057
2097
  break;
2058
2098
  }
2059
2099
  case (38): {
2060
- if ( !angular.isNumber(self.selectedOption)) {
2061
- return;
2100
+ if (!angular.isNumber(self.selectedOption)) {
2101
+ self.selectedOption = elems.length - 1;
2062
2102
  } else {
2063
- self.selectedOption = (self.selectedOption === 0 ?
2064
- 0 :
2065
- self.selectedOption - 1);
2103
+ self.selectedOption = self.selectedOption === 0 ?
2104
+ 0 : self.selectedOption - 1;
2066
2105
  }
2067
2106
  break;
2068
2107
  }
@@ -2075,38 +2114,40 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2075
2114
  };
2076
2115
 
2077
2116
  scope.focusToggleElement = function() {
2078
- if ( self.toggleElement ) {
2117
+ if (self.toggleElement) {
2079
2118
  self.toggleElement[0].focus();
2080
2119
  }
2081
2120
  };
2082
2121
 
2083
- scope.$watch('isOpen', function( isOpen, wasOpen ) {
2122
+ scope.$watch('isOpen', function(isOpen, wasOpen) {
2084
2123
  if (appendToBody && self.dropdownMenu) {
2085
- var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true);
2086
- var css = {
2087
- top: pos.top + 'px',
2088
- display: isOpen ? 'block' : 'none'
2089
- };
2124
+ var pos = $position.positionElements(self.$element, self.dropdownMenu, 'bottom-left', true);
2125
+ var css = {
2126
+ top: pos.top + 'px',
2127
+ display: isOpen ? 'block' : 'none'
2128
+ };
2090
2129
 
2091
- var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
2092
- if (!rightalign) {
2093
- css.left = pos.left + 'px';
2094
- css.right = 'auto';
2095
- } else {
2096
- css.left = 'auto';
2097
- css.right = (window.innerWidth - (pos.left + self.$element.prop('offsetWidth'))) + 'px';
2098
- }
2130
+ var rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
2131
+ if (!rightalign) {
2132
+ css.left = pos.left + 'px';
2133
+ css.right = 'auto';
2134
+ } else {
2135
+ css.left = 'auto';
2136
+ css.right = (window.innerWidth - (pos.left + self.$element.prop('offsetWidth'))) + 'px';
2137
+ }
2099
2138
 
2100
- self.dropdownMenu.css(css);
2139
+ self.dropdownMenu.css(css);
2101
2140
  }
2102
2141
 
2103
- $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass).then(function() {
2104
- if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
2105
- toggleInvoker($scope, { open: !!isOpen });
2106
- }
2142
+ var openContainer = appendToBody ? body : self.$element;
2143
+
2144
+ $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, openClass).then(function() {
2145
+ if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
2146
+ toggleInvoker($scope, { open: !!isOpen });
2147
+ }
2107
2148
  });
2108
2149
 
2109
- if ( isOpen ) {
2150
+ if (isOpen) {
2110
2151
  if (self.dropdownMenuTemplateUrl) {
2111
2152
  $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
2112
2153
  templateScope = scope.$new();
@@ -2119,7 +2160,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2119
2160
  }
2120
2161
 
2121
2162
  scope.focusToggleElement();
2122
- dropdownService.open( scope );
2163
+ dropdownService.open(scope);
2123
2164
  } else {
2124
2165
  if (self.dropdownMenuTemplateUrl) {
2125
2166
  if (templateScope) {
@@ -2130,7 +2171,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2130
2171
  self.dropdownMenu = newEl;
2131
2172
  }
2132
2173
 
2133
- dropdownService.close( scope );
2174
+ dropdownService.close(scope);
2134
2175
  self.selectedOption = null;
2135
2176
  }
2136
2177
 
@@ -2145,9 +2186,10 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2145
2186
  }
2146
2187
  });
2147
2188
 
2148
- $scope.$on('$destroy', function() {
2189
+ var offDestroy = $scope.$on('$destroy', function() {
2149
2190
  scope.$destroy();
2150
2191
  });
2192
+ scope.$on('$destroy', offDestroy);
2151
2193
  }])
2152
2194
 
2153
2195
  .directive('dropdown', function() {
@@ -2186,9 +2228,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2186
2228
  link: function (scope, element, attrs, dropdownCtrl) {
2187
2229
 
2188
2230
  element.bind('keydown', function(e) {
2189
-
2190
2231
  if ([38, 40].indexOf(e.which) !== -1) {
2191
-
2192
2232
  e.preventDefault();
2193
2233
  e.stopPropagation();
2194
2234
 
@@ -2196,24 +2236,28 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2196
2236
 
2197
2237
  switch (e.which) {
2198
2238
  case (40): { // Down
2199
- if ( !angular.isNumber(dropdownCtrl.selectedOption)) {
2239
+ if (!angular.isNumber(dropdownCtrl.selectedOption)) {
2200
2240
  dropdownCtrl.selectedOption = 0;
2201
2241
  } else {
2202
- dropdownCtrl.selectedOption = (dropdownCtrl.selectedOption === elems.length -1 ? dropdownCtrl.selectedOption : dropdownCtrl.selectedOption+1);
2242
+ dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === elems.length -1 ?
2243
+ dropdownCtrl.selectedOption : dropdownCtrl.selectedOption + 1;
2203
2244
  }
2204
-
2245
+ break;
2205
2246
  }
2206
- break;
2207
2247
  case (38): { // Up
2208
- dropdownCtrl.selectedOption = (dropdownCtrl.selectedOption === 0 ? 0 : dropdownCtrl.selectedOption-1);
2248
+ if (!angular.isNumber(dropdownCtrl.selectedOption)) {
2249
+ dropdownCtrl.selectedOption = elems.length - 1;
2250
+ } else {
2251
+ dropdownCtrl.selectedOption = dropdownCtrl.selectedOption === 0 ?
2252
+ 0 : dropdownCtrl.selectedOption - 1;
2253
+ }
2254
+ break;
2209
2255
  }
2210
- break;
2211
2256
  }
2212
2257
  elems[dropdownCtrl.selectedOption].focus();
2213
2258
  }
2214
2259
  });
2215
2260
  }
2216
-
2217
2261
  };
2218
2262
  })
2219
2263
 
@@ -2221,7 +2265,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2221
2265
  return {
2222
2266
  require: '?^dropdown',
2223
2267
  link: function(scope, element, attrs, dropdownCtrl) {
2224
- if ( !dropdownCtrl ) {
2268
+ if (!dropdownCtrl) {
2225
2269
  return;
2226
2270
  }
2227
2271
 
@@ -2232,7 +2276,7 @@ angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
2232
2276
  var toggleDropdown = function(event) {
2233
2277
  event.preventDefault();
2234
2278
 
2235
- if ( !element.hasClass('disabled') && !attrs.disabled ) {
2279
+ if (!element.hasClass('disabled') && !attrs.disabled) {
2236
2280
  scope.$apply(function() {
2237
2281
  dropdownCtrl.toggle();
2238
2282
  });
@@ -2260,19 +2304,19 @@ angular.module('ui.bootstrap.modal', [])
2260
2304
  * A helper, internal data structure that acts as a map but also allows getting / removing
2261
2305
  * elements in the LIFO order
2262
2306
  */
2263
- .factory('$$stackedMap', function () {
2307
+ .factory('$$stackedMap', function() {
2264
2308
  return {
2265
- createNew: function () {
2309
+ createNew: function() {
2266
2310
  var stack = [];
2267
2311
 
2268
2312
  return {
2269
- add: function (key, value) {
2313
+ add: function(key, value) {
2270
2314
  stack.push({
2271
2315
  key: key,
2272
2316
  value: value
2273
2317
  });
2274
2318
  },
2275
- get: function (key) {
2319
+ get: function(key) {
2276
2320
  for (var i = 0; i < stack.length; i++) {
2277
2321
  if (key == stack[i].key) {
2278
2322
  return stack[i];
@@ -2286,10 +2330,10 @@ angular.module('ui.bootstrap.modal', [])
2286
2330
  }
2287
2331
  return keys;
2288
2332
  },
2289
- top: function () {
2333
+ top: function() {
2290
2334
  return stack[stack.length - 1];
2291
2335
  },
2292
- remove: function (key) {
2336
+ remove: function(key) {
2293
2337
  var idx = -1;
2294
2338
  for (var i = 0; i < stack.length; i++) {
2295
2339
  if (key == stack[i].key) {
@@ -2299,10 +2343,10 @@ angular.module('ui.bootstrap.modal', [])
2299
2343
  }
2300
2344
  return stack.splice(idx, 1)[0];
2301
2345
  },
2302
- removeTop: function () {
2346
+ removeTop: function() {
2303
2347
  return stack.splice(stack.length - 1, 1)[0];
2304
2348
  },
2305
- length: function () {
2349
+ length: function() {
2306
2350
  return stack.length;
2307
2351
  }
2308
2352
  };
@@ -2310,12 +2354,67 @@ angular.module('ui.bootstrap.modal', [])
2310
2354
  };
2311
2355
  })
2312
2356
 
2357
+ /**
2358
+ * A helper, internal data structure that stores all references attached to key
2359
+ */
2360
+ .factory('$$multiMap', function() {
2361
+ return {
2362
+ createNew: function() {
2363
+ var map = {};
2364
+
2365
+ return {
2366
+ entries: function() {
2367
+ return Object.keys(map).map(function(key) {
2368
+ return {
2369
+ key: key,
2370
+ value: map[key]
2371
+ };
2372
+ });
2373
+ },
2374
+ get: function(key) {
2375
+ return map[key];
2376
+ },
2377
+ hasKey: function(key) {
2378
+ return !!map[key];
2379
+ },
2380
+ keys: function() {
2381
+ return Object.keys(map);
2382
+ },
2383
+ put: function(key, value) {
2384
+ if (!map[key]) {
2385
+ map[key] = [];
2386
+ }
2387
+
2388
+ map[key].push(value);
2389
+ },
2390
+ remove: function(key, value) {
2391
+ var values = map[key];
2392
+
2393
+ if (!values) {
2394
+ return;
2395
+ }
2396
+
2397
+ var idx = values.indexOf(value);
2398
+
2399
+ if (idx !== -1) {
2400
+ values.splice(idx, 1);
2401
+ }
2402
+
2403
+ if (!values.length) {
2404
+ delete map[key];
2405
+ }
2406
+ }
2407
+ };
2408
+ }
2409
+ };
2410
+ })
2411
+
2313
2412
  /**
2314
2413
  * A helper directive for the $modal service. It creates a backdrop element.
2315
2414
  */
2316
2415
  .directive('modalBackdrop', [
2317
2416
  '$animate', '$injector', '$modalStack',
2318
- function ($animate , $injector, $modalStack) {
2417
+ function($animate , $injector, $modalStack) {
2319
2418
  var $animateCss = null;
2320
2419
 
2321
2420
  if ($injector.has('$animateCss')) {
@@ -2326,7 +2425,7 @@ angular.module('ui.bootstrap.modal', [])
2326
2425
  restrict: 'EA',
2327
2426
  replace: true,
2328
2427
  templateUrl: 'template/modal/backdrop.html',
2329
- compile: function (tElement, tAttrs) {
2428
+ compile: function(tElement, tAttrs) {
2330
2429
  tElement.addClass(tAttrs.backdropClass);
2331
2430
  return linkFn;
2332
2431
  }
@@ -2342,7 +2441,7 @@ angular.module('ui.bootstrap.modal', [])
2342
2441
  $animate.addClass(element, attrs.modalInClass);
2343
2442
  }
2344
2443
 
2345
- scope.$on($modalStack.NOW_CLOSING_EVENT, function (e, setIsAsync) {
2444
+ scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
2346
2445
  var done = setIsAsync();
2347
2446
  if ($animateCss) {
2348
2447
  $animateCss(element, {
@@ -2358,7 +2457,7 @@ angular.module('ui.bootstrap.modal', [])
2358
2457
 
2359
2458
  .directive('modalWindow', [
2360
2459
  '$modalStack', '$q', '$animate', '$injector',
2361
- function ($modalStack , $q , $animate, $injector) {
2460
+ function($modalStack , $q , $animate, $injector) {
2362
2461
  var $animateCss = null;
2363
2462
 
2364
2463
  if ($injector.has('$animateCss')) {
@@ -2375,13 +2474,13 @@ angular.module('ui.bootstrap.modal', [])
2375
2474
  templateUrl: function(tElement, tAttrs) {
2376
2475
  return tAttrs.templateUrl || 'template/modal/window.html';
2377
2476
  },
2378
- link: function (scope, element, attrs) {
2477
+ link: function(scope, element, attrs) {
2379
2478
  element.addClass(attrs.windowClass || '');
2380
2479
  scope.size = attrs.size;
2381
2480
 
2382
- scope.close = function (evt) {
2481
+ scope.close = function(evt) {
2383
2482
  var modal = $modalStack.getTop();
2384
- if (modal && modal.value.backdrop && modal.value.backdrop != 'static' && (evt.target === evt.currentTarget)) {
2483
+ if (modal && modal.value.backdrop && modal.value.backdrop !== 'static' && (evt.target === evt.currentTarget)) {
2385
2484
  evt.preventDefault();
2386
2485
  evt.stopPropagation();
2387
2486
  $modalStack.dismiss(modal.key, 'backdrop click');
@@ -2397,23 +2496,25 @@ angular.module('ui.bootstrap.modal', [])
2397
2496
  var modalRenderDeferObj = $q.defer();
2398
2497
  // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
2399
2498
  // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
2400
- attrs.$observe('modalRender', function (value) {
2499
+ attrs.$observe('modalRender', function(value) {
2401
2500
  if (value == 'true') {
2402
2501
  modalRenderDeferObj.resolve();
2403
2502
  }
2404
2503
  });
2405
2504
 
2406
- modalRenderDeferObj.promise.then(function () {
2505
+ modalRenderDeferObj.promise.then(function() {
2506
+ var animationPromise = null;
2507
+
2407
2508
  if (attrs.modalInClass) {
2408
2509
  if ($animateCss) {
2409
- $animateCss(element, {
2510
+ animationPromise = $animateCss(element, {
2410
2511
  addClass: attrs.modalInClass
2411
2512
  }).start();
2412
2513
  } else {
2413
- $animate.addClass(element, attrs.modalInClass);
2514
+ animationPromise = $animate.addClass(element, attrs.modalInClass);
2414
2515
  }
2415
2516
 
2416
- scope.$on($modalStack.NOW_CLOSING_EVENT, function (e, setIsAsync) {
2517
+ scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
2417
2518
  var done = setIsAsync();
2418
2519
  if ($animateCss) {
2419
2520
  $animateCss(element, {
@@ -2425,20 +2526,23 @@ angular.module('ui.bootstrap.modal', [])
2425
2526
  });
2426
2527
  }
2427
2528
 
2428
- var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]');
2429
- /**
2430
- * Auto-focusing of a freshly-opened modal element causes any child elements
2431
- * with the autofocus attribute to lose focus. This is an issue on touch
2432
- * based devices which will show and then hide the onscreen keyboard.
2433
- * Attempts to refocus the autofocus element via JavaScript will not reopen
2434
- * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
2435
- * the modal element if the modal does not contain an autofocus element.
2436
- */
2437
- if (inputsWithAutofocus.length) {
2438
- inputsWithAutofocus[0].focus();
2439
- } else {
2440
- element[0].focus();
2441
- }
2529
+
2530
+ $q.when(animationPromise).then(function() {
2531
+ var inputsWithAutofocus = element[0].querySelectorAll('[autofocus]');
2532
+ /**
2533
+ * Auto-focusing of a freshly-opened modal element causes any child elements
2534
+ * with the autofocus attribute to lose focus. This is an issue on touch
2535
+ * based devices which will show and then hide the onscreen keyboard.
2536
+ * Attempts to refocus the autofocus element via JavaScript will not reopen
2537
+ * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
2538
+ * the modal element if the modal does not contain an autofocus element.
2539
+ */
2540
+ if (inputsWithAutofocus.length) {
2541
+ inputsWithAutofocus[0].focus();
2542
+ } else {
2543
+ element[0].focus();
2544
+ }
2545
+ });
2442
2546
 
2443
2547
  // Notify {@link $modalStack} that modal is rendered.
2444
2548
  var modal = $modalStack.getTop();
@@ -2453,7 +2557,7 @@ angular.module('ui.bootstrap.modal', [])
2453
2557
  .directive('modalAnimationClass', [
2454
2558
  function () {
2455
2559
  return {
2456
- compile: function (tElement, tAttrs) {
2560
+ compile: function(tElement, tAttrs) {
2457
2561
  if (tAttrs.modalAnimation) {
2458
2562
  tElement.addClass(tAttrs.modalAnimationClass);
2459
2563
  }
@@ -2461,7 +2565,7 @@ angular.module('ui.bootstrap.modal', [])
2461
2565
  };
2462
2566
  }])
2463
2567
 
2464
- .directive('modalTransclude', function () {
2568
+ .directive('modalTransclude', function() {
2465
2569
  return {
2466
2570
  link: function($scope, $element, $attrs, controller, $transclude) {
2467
2571
  $transclude($scope.$parent, function(clone) {
@@ -2476,10 +2580,12 @@ angular.module('ui.bootstrap.modal', [])
2476
2580
  '$animate', '$timeout', '$document', '$compile', '$rootScope',
2477
2581
  '$q',
2478
2582
  '$injector',
2583
+ '$$multiMap',
2479
2584
  '$$stackedMap',
2480
- function ($animate , $timeout , $document , $compile , $rootScope ,
2585
+ function($animate , $timeout , $document , $compile , $rootScope ,
2481
2586
  $q,
2482
2587
  $injector,
2588
+ $$multiMap,
2483
2589
  $$stackedMap) {
2484
2590
  var $animateCss = null;
2485
2591
 
@@ -2491,6 +2597,7 @@ angular.module('ui.bootstrap.modal', [])
2491
2597
 
2492
2598
  var backdropDomEl, backdropScope;
2493
2599
  var openedWindows = $$stackedMap.createNew();
2600
+ var openedClasses = $$multiMap.createNew();
2494
2601
  var $modalStack = {
2495
2602
  NOW_CLOSING_EVENT: 'modal.stack.now-closing'
2496
2603
  };
@@ -2520,7 +2627,6 @@ angular.module('ui.bootstrap.modal', [])
2520
2627
  });
2521
2628
 
2522
2629
  function removeModalWindow(modalInstance, elementToReceiveFocus) {
2523
-
2524
2630
  var body = $document.find('body').eq(0);
2525
2631
  var modalWindow = openedWindows.get(modalInstance).value;
2526
2632
 
@@ -2528,7 +2634,9 @@ angular.module('ui.bootstrap.modal', [])
2528
2634
  openedWindows.remove(modalInstance);
2529
2635
 
2530
2636
  removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
2531
- body.toggleClass(modalInstance.openedClass || OPENED_MODAL_CLASS, openedWindows.length() > 0);
2637
+ var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
2638
+ openedClasses.remove(modalBodyClass, modalInstance);
2639
+ body.toggleClass(modalBodyClass, openedClasses.hasKey(modalBodyClass));
2532
2640
  });
2533
2641
  checkRemoveBackdrop();
2534
2642
 
@@ -2544,7 +2652,7 @@ angular.module('ui.bootstrap.modal', [])
2544
2652
  //remove backdrop if no longer needed
2545
2653
  if (backdropDomEl && backdropIndex() == -1) {
2546
2654
  var backdropScopeRef = backdropScope;
2547
- removeAfterAnimate(backdropDomEl, backdropScope, function () {
2655
+ removeAfterAnimate(backdropDomEl, backdropScope, function() {
2548
2656
  backdropScopeRef = null;
2549
2657
  });
2550
2658
  backdropDomEl = undefined;
@@ -2555,7 +2663,7 @@ angular.module('ui.bootstrap.modal', [])
2555
2663
  function removeAfterAnimate(domEl, scope, done) {
2556
2664
  var asyncDeferred;
2557
2665
  var asyncPromise = null;
2558
- var setIsAsync = function () {
2666
+ var setIsAsync = function() {
2559
2667
  if (!asyncDeferred) {
2560
2668
  asyncDeferred = $q.defer();
2561
2669
  asyncPromise = asyncDeferred.promise;
@@ -2594,7 +2702,7 @@ angular.module('ui.bootstrap.modal', [])
2594
2702
  }
2595
2703
  }
2596
2704
 
2597
- $document.bind('keydown', function (evt) {
2705
+ $document.bind('keydown', function(evt) {
2598
2706
  if (evt.isDefaultPrevented()) {
2599
2707
  return evt;
2600
2708
  }
@@ -2604,7 +2712,7 @@ angular.module('ui.bootstrap.modal', [])
2604
2712
  switch (evt.which){
2605
2713
  case 27: {
2606
2714
  evt.preventDefault();
2607
- $rootScope.$apply(function () {
2715
+ $rootScope.$apply(function() {
2608
2716
  $modalStack.dismiss(modal.key, 'escape key press');
2609
2717
  });
2610
2718
  break;
@@ -2632,9 +2740,9 @@ angular.module('ui.bootstrap.modal', [])
2632
2740
  }
2633
2741
  });
2634
2742
 
2635
- $modalStack.open = function (modalInstance, modal) {
2636
-
2637
- var modalOpener = $document[0].activeElement;
2743
+ $modalStack.open = function(modalInstance, modal) {
2744
+ var modalOpener = $document[0].activeElement,
2745
+ modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
2638
2746
 
2639
2747
  openedWindows.add(modalInstance, {
2640
2748
  deferred: modal.deferred,
@@ -2645,6 +2753,8 @@ angular.module('ui.bootstrap.modal', [])
2645
2753
  openedClass: modal.openedClass
2646
2754
  });
2647
2755
 
2756
+ openedClasses.put(modalBodyClass, modalInstance);
2757
+
2648
2758
  var body = $document.find('body').eq(0),
2649
2759
  currBackdropIndex = backdropIndex();
2650
2760
 
@@ -2676,7 +2786,8 @@ angular.module('ui.bootstrap.modal', [])
2676
2786
  openedWindows.top().value.modalDomEl = modalDomEl;
2677
2787
  openedWindows.top().value.modalOpener = modalOpener;
2678
2788
  body.append(modalDomEl);
2679
- body.addClass(modal.openedClass || OPENED_MODAL_CLASS);
2789
+ body.addClass(modalBodyClass);
2790
+
2680
2791
  $modalStack.clearFocusListCache();
2681
2792
  };
2682
2793
 
@@ -2684,7 +2795,7 @@ angular.module('ui.bootstrap.modal', [])
2684
2795
  return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
2685
2796
  }
2686
2797
 
2687
- $modalStack.close = function (modalInstance, result) {
2798
+ $modalStack.close = function(modalInstance, result) {
2688
2799
  var modalWindow = openedWindows.get(modalInstance);
2689
2800
  if (modalWindow && broadcastClosing(modalWindow, result, true)) {
2690
2801
  modalWindow.value.modalScope.$$uibDestructionScheduled = true;
@@ -2695,7 +2806,7 @@ angular.module('ui.bootstrap.modal', [])
2695
2806
  return !modalWindow;
2696
2807
  };
2697
2808
 
2698
- $modalStack.dismiss = function (modalInstance, reason) {
2809
+ $modalStack.dismiss = function(modalInstance, reason) {
2699
2810
  var modalWindow = openedWindows.get(modalInstance);
2700
2811
  if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
2701
2812
  modalWindow.value.modalScope.$$uibDestructionScheduled = true;
@@ -2706,18 +2817,18 @@ angular.module('ui.bootstrap.modal', [])
2706
2817
  return !modalWindow;
2707
2818
  };
2708
2819
 
2709
- $modalStack.dismissAll = function (reason) {
2820
+ $modalStack.dismissAll = function(reason) {
2710
2821
  var topModal = this.getTop();
2711
2822
  while (topModal && this.dismiss(topModal.key, reason)) {
2712
2823
  topModal = this.getTop();
2713
2824
  }
2714
2825
  };
2715
2826
 
2716
- $modalStack.getTop = function () {
2827
+ $modalStack.getTop = function() {
2717
2828
  return openedWindows.top();
2718
2829
  };
2719
2830
 
2720
- $modalStack.modalRendered = function (modalInstance) {
2831
+ $modalStack.modalRendered = function(modalInstance) {
2721
2832
  var modalWindow = openedWindows.get(modalInstance);
2722
2833
  if (modalWindow) {
2723
2834
  modalWindow.value.renderDeferred.resolve();
@@ -2772,8 +2883,7 @@ angular.module('ui.bootstrap.modal', [])
2772
2883
  return $modalStack;
2773
2884
  }])
2774
2885
 
2775
- .provider('$modal', function () {
2776
-
2886
+ .provider('$modal', function() {
2777
2887
  var $modalProvider = {
2778
2888
  options: {
2779
2889
  animation: true,
@@ -2782,7 +2892,6 @@ angular.module('ui.bootstrap.modal', [])
2782
2892
  },
2783
2893
  $get: ['$injector', '$rootScope', '$q', '$templateRequest', '$controller', '$modalStack',
2784
2894
  function ($injector, $rootScope, $q, $templateRequest, $controller, $modalStack) {
2785
-
2786
2895
  var $modal = {};
2787
2896
 
2788
2897
  function getTemplatePromise(options) {
@@ -2792,16 +2901,23 @@ angular.module('ui.bootstrap.modal', [])
2792
2901
 
2793
2902
  function getResolvePromises(resolves) {
2794
2903
  var promisesArr = [];
2795
- angular.forEach(resolves, function (value) {
2904
+ angular.forEach(resolves, function(value) {
2796
2905
  if (angular.isFunction(value) || angular.isArray(value)) {
2797
2906
  promisesArr.push($q.when($injector.invoke(value)));
2798
2907
  } else if (angular.isString(value)) {
2799
2908
  promisesArr.push($q.when($injector.get(value)));
2909
+ } else {
2910
+ promisesArr.push($q.when(value));
2800
2911
  }
2801
2912
  });
2802
2913
  return promisesArr;
2803
2914
  }
2804
2915
 
2916
+ var promiseChain = null;
2917
+ $modal.getPromiseChain = function() {
2918
+ return promiseChain;
2919
+ };
2920
+
2805
2921
  $modal.open = function (modalOptions) {
2806
2922
 
2807
2923
  var modalResultDeferred = $q.defer();
@@ -2833,63 +2949,70 @@ angular.module('ui.bootstrap.modal', [])
2833
2949
  var templateAndResolvePromise =
2834
2950
  $q.all([getTemplatePromise(modalOptions)].concat(getResolvePromises(modalOptions.resolve)));
2835
2951
 
2952
+ // Wait for the resolution of the existing promise chain.
2953
+ // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
2954
+ // Then add to $modalStack and resolve opened.
2955
+ // Finally clean up the chain variable if no subsequent modal has overwritten it.
2956
+ var samePromise;
2957
+ samePromise = promiseChain = $q.all([promiseChain])
2958
+ .then(function() { return templateAndResolvePromise; }, function() { return templateAndResolvePromise; })
2959
+ .then(function resolveSuccess(tplAndVars) {
2960
+
2961
+ var modalScope = (modalOptions.scope || $rootScope).$new();
2962
+ modalScope.$close = modalInstance.close;
2963
+ modalScope.$dismiss = modalInstance.dismiss;
2964
+
2965
+ modalScope.$on('$destroy', function() {
2966
+ if (!modalScope.$$uibDestructionScheduled) {
2967
+ modalScope.$dismiss('$uibUnscheduledDestruction');
2968
+ }
2969
+ });
2836
2970
 
2837
- templateAndResolvePromise.then(function resolveSuccess(tplAndVars) {
2971
+ var ctrlInstance, ctrlLocals = {};
2972
+ var resolveIter = 1;
2838
2973
 
2839
- var modalScope = (modalOptions.scope || $rootScope).$new();
2840
- modalScope.$close = modalInstance.close;
2841
- modalScope.$dismiss = modalInstance.dismiss;
2974
+ //controllers
2975
+ if (modalOptions.controller) {
2976
+ ctrlLocals.$scope = modalScope;
2977
+ ctrlLocals.$modalInstance = modalInstance;
2978
+ angular.forEach(modalOptions.resolve, function(value, key) {
2979
+ ctrlLocals[key] = tplAndVars[resolveIter++];
2980
+ });
2842
2981
 
2843
- modalScope.$on('$destroy', function() {
2844
- if (!modalScope.$$uibDestructionScheduled) {
2845
- modalScope.$dismiss('$uibUnscheduledDestruction');
2846
- }
2847
- });
2982
+ ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
2983
+ if (modalOptions.controllerAs) {
2984
+ if (modalOptions.bindToController) {
2985
+ angular.extend(ctrlInstance, modalScope);
2986
+ }
2848
2987
 
2849
- var ctrlInstance, ctrlLocals = {};
2850
- var resolveIter = 1;
2851
-
2852
- //controllers
2853
- if (modalOptions.controller) {
2854
- ctrlLocals.$scope = modalScope;
2855
- ctrlLocals.$modalInstance = modalInstance;
2856
- angular.forEach(modalOptions.resolve, function (value, key) {
2857
- ctrlLocals[key] = tplAndVars[resolveIter++];
2858
- });
2859
-
2860
- ctrlInstance = $controller(modalOptions.controller, ctrlLocals);
2861
- if (modalOptions.controllerAs) {
2862
- if (modalOptions.bindToController) {
2863
- angular.extend(ctrlInstance, modalScope);
2988
+ modalScope[modalOptions.controllerAs] = ctrlInstance;
2864
2989
  }
2865
-
2866
- modalScope[modalOptions.controllerAs] = ctrlInstance;
2867
2990
  }
2868
- }
2869
2991
 
2870
- $modalStack.open(modalInstance, {
2871
- scope: modalScope,
2872
- deferred: modalResultDeferred,
2873
- renderDeferred: modalRenderDeferred,
2874
- content: tplAndVars[0],
2875
- animation: modalOptions.animation,
2876
- backdrop: modalOptions.backdrop,
2877
- keyboard: modalOptions.keyboard,
2878
- backdropClass: modalOptions.backdropClass,
2879
- windowClass: modalOptions.windowClass,
2880
- windowTemplateUrl: modalOptions.windowTemplateUrl,
2881
- size: modalOptions.size,
2882
- openedClass: modalOptions.openedClass
2883
- });
2992
+ $modalStack.open(modalInstance, {
2993
+ scope: modalScope,
2994
+ deferred: modalResultDeferred,
2995
+ renderDeferred: modalRenderDeferred,
2996
+ content: tplAndVars[0],
2997
+ animation: modalOptions.animation,
2998
+ backdrop: modalOptions.backdrop,
2999
+ keyboard: modalOptions.keyboard,
3000
+ backdropClass: modalOptions.backdropClass,
3001
+ windowClass: modalOptions.windowClass,
3002
+ windowTemplateUrl: modalOptions.windowTemplateUrl,
3003
+ size: modalOptions.size,
3004
+ openedClass: modalOptions.openedClass
3005
+ });
3006
+ modalOpenedDeferred.resolve(true);
2884
3007
 
2885
3008
  }, function resolveError(reason) {
2886
- modalResultDeferred.reject(reason);
2887
- });
2888
-
2889
- templateAndResolvePromise.then(function () {
2890
- modalOpenedDeferred.resolve(true);
2891
- }, function (reason) {
2892
3009
  modalOpenedDeferred.reject(reason);
3010
+ modalResultDeferred.reject(reason);
3011
+ })
3012
+ .finally(function() {
3013
+ if (promiseChain === samePromise) {
3014
+ promiseChain = null;
3015
+ }
2893
3016
  });
2894
3017
 
2895
3018
  return modalInstance;
@@ -2903,7 +3026,7 @@ angular.module('ui.bootstrap.modal', [])
2903
3026
  });
2904
3027
 
2905
3028
  angular.module('ui.bootstrap.pagination', [])
2906
- .controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) {
3029
+ .controller('PaginationController', ['$scope', '$attrs', '$parse', function($scope, $attrs, $parse) {
2907
3030
  var self = this,
2908
3031
  ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
2909
3032
  setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
@@ -2964,12 +3087,14 @@ angular.module('ui.bootstrap.pagination', [])
2964
3087
  }
2965
3088
  };
2966
3089
 
2967
- $scope.getText = function( key ) {
3090
+ $scope.getText = function(key) {
2968
3091
  return $scope[key + 'Text'] || self.config[key + 'Text'];
2969
3092
  };
3093
+
2970
3094
  $scope.noPrevious = function() {
2971
3095
  return $scope.page === 1;
2972
3096
  };
3097
+
2973
3098
  $scope.noNext = function() {
2974
3099
  return $scope.page === $scope.totalPages;
2975
3100
  };
@@ -3040,11 +3165,11 @@ angular.module('ui.bootstrap.pagination', [])
3040
3165
 
3041
3166
  // Default page limits
3042
3167
  var startPage = 1, endPage = totalPages;
3043
- var isMaxSized = ( angular.isDefined(maxSize) && maxSize < totalPages );
3168
+ var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
3044
3169
 
3045
3170
  // recompute if maxSize
3046
- if ( isMaxSized ) {
3047
- if ( rotate ) {
3171
+ if (isMaxSized) {
3172
+ if (rotate) {
3048
3173
  // Current page is displayed in the middle of the visible ones
3049
3174
  startPage = Math.max(currentPage - Math.floor(maxSize/2), 1);
3050
3175
  endPage = startPage + maxSize - 1;
@@ -3070,13 +3195,13 @@ angular.module('ui.bootstrap.pagination', [])
3070
3195
  }
3071
3196
 
3072
3197
  // Add links to move between page sets
3073
- if ( isMaxSized && ! rotate ) {
3074
- if ( startPage > 1 ) {
3198
+ if (isMaxSized && ! rotate) {
3199
+ if (startPage > 1) {
3075
3200
  var previousPageSet = makePage(startPage - 1, '...', false);
3076
3201
  pages.unshift(previousPageSet);
3077
3202
  }
3078
3203
 
3079
- if ( endPage < totalPages ) {
3204
+ if (endPage < totalPages) {
3080
3205
  var nextPageSet = makePage(endPage + 1, '...', false);
3081
3206
  pages.push(nextPageSet);
3082
3207
  }
@@ -3109,11 +3234,15 @@ angular.module('ui.bootstrap.pagination', [])
3109
3234
  scope: {
3110
3235
  totalItems: '=',
3111
3236
  previousText: '@',
3112
- nextText: '@'
3237
+ nextText: '@',
3238
+ ngDisabled: '='
3113
3239
  },
3114
3240
  require: ['pager', '?ngModel'],
3115
3241
  controller: 'PaginationController',
3116
- templateUrl: 'template/pagination/pager.html',
3242
+ controllerAs: 'pagination',
3243
+ templateUrl: function(element, attrs) {
3244
+ return attrs.templateUrl || 'template/pagination/pager.html';
3245
+ },
3117
3246
  replace: true,
3118
3247
  link: function(scope, element, attrs, ctrls) {
3119
3248
  var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
@@ -3133,13 +3262,13 @@ angular.module('ui.bootstrap.pagination', [])
3133
3262
  * function, placement as a function, inside, support for more triggers than
3134
3263
  * just mouse enter/leave, html tooltips, and selector delegation.
3135
3264
  */
3136
- angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap.bindHtml' ] )
3265
+ angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3137
3266
 
3138
3267
  /**
3139
3268
  * The $tooltip service creates tooltip- and popover-like directives as well as
3140
3269
  * houses global options for them.
3141
3270
  */
3142
- .provider( '$tooltip', function () {
3271
+ .provider('$tooltip', function() {
3143
3272
  // The default options tooltip and popover.
3144
3273
  var defaultOptions = {
3145
3274
  placement: 'top',
@@ -3152,7 +3281,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3152
3281
  var triggerMap = {
3153
3282
  'mouseenter': 'mouseleave',
3154
3283
  'click': 'click',
3155
- 'focus': 'blur'
3284
+ 'focus': 'blur',
3285
+ 'none': ''
3156
3286
  };
3157
3287
 
3158
3288
  // The options specified to the provider globally.
@@ -3167,23 +3297,23 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3167
3297
  * $tooltipProvider.options( { placement: 'left' } );
3168
3298
  * });
3169
3299
  */
3170
- this.options = function( value ) {
3171
- angular.extend( globalOptions, value );
3172
- };
3300
+ this.options = function(value) {
3301
+ angular.extend(globalOptions, value);
3302
+ };
3173
3303
 
3174
3304
  /**
3175
3305
  * This allows you to extend the set of trigger mappings available. E.g.:
3176
3306
  *
3177
3307
  * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
3178
3308
  */
3179
- this.setTriggers = function setTriggers ( triggers ) {
3180
- angular.extend( triggerMap, triggers );
3309
+ this.setTriggers = function setTriggers(triggers) {
3310
+ angular.extend(triggerMap, triggers);
3181
3311
  };
3182
3312
 
3183
3313
  /**
3184
3314
  * This is a helper function for translating camel-case to snake-case.
3185
3315
  */
3186
- function snake_case(name){
3316
+ function snake_case(name) {
3187
3317
  var regexp = /[A-Z]/g;
3188
3318
  var separator = '-';
3189
3319
  return name.replace(regexp, function(letter, pos) {
@@ -3195,9 +3325,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3195
3325
  * Returns the actual instance of the $tooltip service.
3196
3326
  * TODO support multiple triggers
3197
3327
  */
3198
- this.$get = [ '$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', function ( $window, $compile, $timeout, $document, $position, $interpolate, $rootScope ) {
3199
- return function $tooltip ( type, prefix, defaultTriggerShow, options ) {
3200
- options = angular.extend( {}, defaultOptions, globalOptions, options );
3328
+ this.$get = ['$window', '$compile', '$timeout', '$document', '$position', '$interpolate', '$rootScope', '$parse', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse) {
3329
+ return function $tooltip(type, prefix, defaultTriggerShow, options) {
3330
+ options = angular.extend({}, defaultOptions, globalOptions, options);
3201
3331
 
3202
3332
  /**
3203
3333
  * Returns an object of show and hide triggers.
@@ -3213,7 +3343,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3213
3343
  * undefined; otherwise, it uses the `triggerMap` value of the show
3214
3344
  * trigger; else it will just use the show trigger.
3215
3345
  */
3216
- function getTriggers ( trigger ) {
3346
+ function getTriggers(trigger) {
3217
3347
  var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
3218
3348
  var hide = show.map(function(trigger) {
3219
3349
  return triggerMap[trigger] || trigger;
@@ -3224,7 +3354,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3224
3354
  };
3225
3355
  }
3226
3356
 
3227
- var directiveName = snake_case( type );
3357
+ var directiveName = snake_case(type);
3228
3358
 
3229
3359
  var startSym = $interpolate.startSymbol();
3230
3360
  var endSym = $interpolate.endSymbol();
@@ -3244,33 +3374,45 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3244
3374
 
3245
3375
  return {
3246
3376
  restrict: 'EA',
3247
- compile: function (tElem, tAttrs) {
3377
+ compile: function(tElem, tAttrs) {
3248
3378
  var tooltipLinker = $compile( template );
3249
3379
 
3250
- return function link ( scope, element, attrs, tooltipCtrl ) {
3380
+ return function link(scope, element, attrs, tooltipCtrl) {
3251
3381
  var tooltip;
3252
3382
  var tooltipLinkedScope;
3253
3383
  var transitionTimeout;
3254
3384
  var popupTimeout;
3255
- var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
3256
- var triggers = getTriggers( undefined );
3257
- var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
3385
+ var positionTimeout;
3386
+ var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
3387
+ var triggers = getTriggers(undefined);
3388
+ var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
3258
3389
  var ttScope = scope.$new(true);
3259
3390
  var repositionScheduled = false;
3391
+ var isOpenExp = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
3260
3392
 
3261
- var positionTooltip = function () {
3393
+ var positionTooltip = function() {
3262
3394
  if (!tooltip) { return; }
3263
3395
 
3264
- var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
3265
- ttPosition.top += 'px';
3266
- ttPosition.left += 'px';
3396
+ if (!positionTimeout) {
3397
+ positionTimeout = $timeout(function() {
3398
+ // Reset the positioning and box size for correct width and height values.
3399
+ tooltip.css({ top: 0, left: 0, width: 'auto', height: 'auto' });
3267
3400
 
3268
- // Now set the calculated positioning.
3269
- tooltip.css( ttPosition );
3270
- };
3401
+ var ttBox = $position.position(tooltip);
3402
+ var ttCss = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
3403
+ ttCss.top += 'px';
3404
+ ttCss.left += 'px';
3405
+
3406
+ ttCss.width = ttBox.width + 'px';
3407
+ ttCss.height = ttBox.height + 'px';
3408
+
3409
+ // Now set the calculated positioning and size.
3410
+ tooltip.css(ttCss);
3271
3411
 
3272
- var positionTooltipAsync = function () {
3273
- $timeout(positionTooltip, 0, false);
3412
+ positionTimeout = null;
3413
+
3414
+ }, 0, false);
3415
+ }
3274
3416
  };
3275
3417
 
3276
3418
  // Set up the correct scope to allow transclusion later
@@ -3280,8 +3422,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3280
3422
  // TODO add ability to start tooltip opened
3281
3423
  ttScope.isOpen = false;
3282
3424
 
3283
- function toggleTooltipBind () {
3284
- if ( ! ttScope.isOpen ) {
3425
+ function toggleTooltipBind() {
3426
+ if (!ttScope.isOpen) {
3285
3427
  showTooltipBind();
3286
3428
  } else {
3287
3429
  hideTooltipBind();
@@ -3290,21 +3432,20 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3290
3432
 
3291
3433
  // Show the tooltip with delay if specified, otherwise show it immediately
3292
3434
  function showTooltipBind() {
3293
- if(hasEnableExp && !scope.$eval(attrs[prefix+'Enable'])) {
3435
+ if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
3294
3436
  return;
3295
3437
  }
3296
3438
 
3297
3439
  prepareTooltip();
3298
3440
 
3299
- if ( ttScope.popupDelay ) {
3441
+ if (ttScope.popupDelay) {
3300
3442
  // Do nothing if the tooltip was already scheduled to pop-up.
3301
3443
  // This happens if show is triggered multiple times before any hide is triggered.
3302
3444
  if (!popupTimeout) {
3303
- popupTimeout = $timeout( show, ttScope.popupDelay, false );
3304
- popupTimeout.then(function(reposition){reposition();});
3445
+ popupTimeout = $timeout(show, ttScope.popupDelay, false);
3305
3446
  }
3306
3447
  } else {
3307
- show()();
3448
+ show();
3308
3449
  }
3309
3450
  }
3310
3451
 
@@ -3317,50 +3458,56 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3317
3458
 
3318
3459
  // Show the tooltip popup element.
3319
3460
  function show() {
3320
-
3321
3461
  popupTimeout = null;
3322
3462
 
3323
3463
  // If there is a pending remove transition, we must cancel it, lest the
3324
3464
  // tooltip be mysteriously removed.
3325
- if ( transitionTimeout ) {
3326
- $timeout.cancel( transitionTimeout );
3465
+ if (transitionTimeout) {
3466
+ $timeout.cancel(transitionTimeout);
3327
3467
  transitionTimeout = null;
3328
3468
  }
3329
3469
 
3330
3470
  // Don't show empty tooltips.
3331
- if ( !(options.useContentExp ? ttScope.contentExp() : ttScope.content) ) {
3471
+ if (!(options.useContentExp ? ttScope.contentExp() : ttScope.content)) {
3332
3472
  return angular.noop;
3333
3473
  }
3334
3474
 
3335
3475
  createTooltip();
3336
3476
 
3337
- // Set the initial positioning.
3338
- tooltip.css({ top: 0, left: 0, display: 'block' });
3339
-
3340
- positionTooltip();
3341
-
3342
3477
  // And show the tooltip.
3343
3478
  ttScope.isOpen = true;
3344
- ttScope.$apply(); // digest required as $apply is not called
3479
+ if (isOpenExp) {
3480
+ isOpenExp.assign(ttScope.origScope, ttScope.isOpen);
3481
+ }
3482
+
3483
+ if (!$rootScope.$$phase) {
3484
+ ttScope.$apply(); // digest required as $apply is not called
3485
+ }
3345
3486
 
3346
- // Return positioning function as promise callback for correct
3347
- // positioning after draw.
3348
- return positionTooltip;
3487
+ tooltip.css({ display: 'block' });
3488
+
3489
+ positionTooltip();
3349
3490
  }
3350
3491
 
3351
3492
  // Hide the tooltip popup element.
3352
3493
  function hide() {
3353
3494
  // First things first: we don't show it anymore.
3354
3495
  ttScope.isOpen = false;
3496
+ if (isOpenExp) {
3497
+ isOpenExp.assign(ttScope.origScope, ttScope.isOpen);
3498
+ }
3355
3499
 
3356
3500
  //if tooltip is going to be shown after delay, we must cancel this
3357
- $timeout.cancel( popupTimeout );
3501
+ $timeout.cancel(popupTimeout);
3358
3502
  popupTimeout = null;
3359
3503
 
3504
+ $timeout.cancel(positionTimeout);
3505
+ positionTimeout = null;
3506
+
3360
3507
  // And now we remove it from the DOM. However, if we have animation, we
3361
3508
  // need to wait for it to expire beforehand.
3362
3509
  // FIXME: this is a placeholder for a port of the transitions library.
3363
- if ( ttScope.animation ) {
3510
+ if (ttScope.animation) {
3364
3511
  if (!transitionTimeout) {
3365
3512
  transitionTimeout = $timeout(removeTooltip, 500);
3366
3513
  }
@@ -3375,16 +3522,16 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3375
3522
  removeTooltip();
3376
3523
  }
3377
3524
  tooltipLinkedScope = ttScope.$new();
3378
- tooltip = tooltipLinker(tooltipLinkedScope, function (tooltip) {
3379
- if ( appendToBody ) {
3380
- $document.find( 'body' ).append( tooltip );
3525
+ tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
3526
+ if (appendToBody) {
3527
+ $document.find('body').append(tooltip);
3381
3528
  } else {
3382
- element.after( tooltip );
3529
+ element.after(tooltip);
3383
3530
  }
3384
3531
  });
3385
3532
 
3386
3533
  if (options.useContentExp) {
3387
- tooltipLinkedScope.$watch('contentExp()', function (val) {
3534
+ tooltipLinkedScope.$watch('contentExp()', function(val) {
3388
3535
  if (!val && ttScope.isOpen) {
3389
3536
  hide();
3390
3537
  }
@@ -3395,7 +3542,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3395
3542
  repositionScheduled = true;
3396
3543
  tooltipLinkedScope.$$postDigest(function() {
3397
3544
  repositionScheduled = false;
3398
- positionTooltipAsync();
3545
+ if (ttScope.isOpen) {
3546
+ positionTooltip();
3547
+ }
3399
3548
  });
3400
3549
  }
3401
3550
  });
@@ -3421,7 +3570,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3421
3570
  prepPopupDelay();
3422
3571
  }
3423
3572
 
3424
- ttScope.contentExp = function () {
3573
+ ttScope.contentExp = function() {
3425
3574
  return scope.$eval(attrs[type]);
3426
3575
  };
3427
3576
 
@@ -3429,57 +3578,64 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3429
3578
  * Observe the relevant attributes.
3430
3579
  */
3431
3580
  if (!options.useContentExp) {
3432
- attrs.$observe( type, function ( val ) {
3581
+ attrs.$observe(type, function(val) {
3433
3582
  ttScope.content = val;
3434
3583
 
3435
3584
  if (!val && ttScope.isOpen) {
3436
3585
  hide();
3437
3586
  } else {
3438
- positionTooltipAsync();
3587
+ positionTooltip();
3439
3588
  }
3440
3589
  });
3441
3590
  }
3442
3591
 
3443
- attrs.$observe( 'disabled', function ( val ) {
3592
+ attrs.$observe('disabled', function(val) {
3444
3593
  if (popupTimeout && val) {
3445
3594
  $timeout.cancel(popupTimeout);
3595
+ popupTimeout = null;
3446
3596
  }
3447
3597
 
3448
- if (val && ttScope.isOpen ) {
3598
+ if (val && ttScope.isOpen) {
3449
3599
  hide();
3450
3600
  }
3451
3601
  });
3452
3602
 
3453
- attrs.$observe( prefix+'Title', function ( val ) {
3603
+ attrs.$observe(prefix + 'Title', function(val) {
3454
3604
  ttScope.title = val;
3455
- positionTooltipAsync();
3605
+ positionTooltip();
3456
3606
  });
3457
3607
 
3458
- attrs.$observe( prefix + 'Placement', function () {
3608
+ attrs.$observe(prefix + 'Placement', function() {
3459
3609
  if (ttScope.isOpen) {
3460
- $timeout(function () {
3461
- prepPlacement();
3462
- show()();
3463
- }, 0, false);
3610
+ prepPlacement();
3611
+ positionTooltip();
3464
3612
  }
3465
3613
  });
3466
3614
 
3615
+ if (isOpenExp) {
3616
+ scope.$watch(isOpenExp, function(val) {
3617
+ if (val !== ttScope.isOpen) {
3618
+ toggleTooltipBind();
3619
+ }
3620
+ });
3621
+ }
3622
+
3467
3623
  function prepPopupClass() {
3468
3624
  ttScope.popupClass = attrs[prefix + 'Class'];
3469
3625
  }
3470
3626
 
3471
3627
  function prepPlacement() {
3472
- var val = attrs[ prefix + 'Placement' ];
3473
- ttScope.placement = angular.isDefined( val ) ? val : options.placement;
3628
+ var val = attrs[prefix + 'Placement'];
3629
+ ttScope.placement = angular.isDefined(val) ? val : options.placement;
3474
3630
  }
3475
3631
 
3476
3632
  function prepPopupDelay() {
3477
- var val = attrs[ prefix + 'PopupDelay' ];
3478
- var delay = parseInt( val, 10 );
3479
- ttScope.popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
3633
+ var val = attrs[prefix + 'PopupDelay'];
3634
+ var delay = parseInt(val, 10);
3635
+ ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
3480
3636
  }
3481
3637
 
3482
- var unregisterTriggers = function () {
3638
+ var unregisterTriggers = function() {
3483
3639
  triggers.show.forEach(function(trigger) {
3484
3640
  element.unbind(trigger, showTooltipBind);
3485
3641
  });
@@ -3489,19 +3645,22 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3489
3645
  };
3490
3646
 
3491
3647
  function prepTriggers() {
3492
- var val = attrs[ prefix + 'Trigger' ];
3648
+ var val = attrs[prefix + 'Trigger'];
3493
3649
  unregisterTriggers();
3494
3650
 
3495
- triggers = getTriggers( val );
3651
+ triggers = getTriggers(val);
3496
3652
 
3497
- triggers.show.forEach(function(trigger, idx) {
3498
- if (trigger === triggers.hide[idx]) {
3499
- element.bind(trigger, toggleTooltipBind);
3500
- } else if (trigger) {
3501
- element.bind(trigger, showTooltipBind);
3502
- element.bind(triggers.hide[idx], hideTooltipBind);
3503
- }
3504
- });
3653
+ if (triggers.show !== 'none') {
3654
+ triggers.show.forEach(function(trigger, idx) {
3655
+ // Using raw addEventListener due to jqLite/jQuery bug - #4060
3656
+ if (trigger === triggers.hide[idx]) {
3657
+ element[0].addEventListener(trigger, toggleTooltipBind);
3658
+ } else if (trigger) {
3659
+ element[0].addEventListener(trigger, showTooltipBind);
3660
+ element[0].addEventListener(triggers.hide[idx], hideTooltipBind);
3661
+ }
3662
+ });
3663
+ }
3505
3664
  }
3506
3665
  prepTriggers();
3507
3666
 
@@ -3514,18 +3673,19 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3514
3673
  // if a tooltip is attached to <body> we need to remove it on
3515
3674
  // location change as its parent scope will probably not be destroyed
3516
3675
  // by the change.
3517
- if ( appendToBody ) {
3518
- scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
3519
- if ( ttScope.isOpen ) {
3520
- hide();
3521
- }
3522
- });
3676
+ if (appendToBody) {
3677
+ scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess() {
3678
+ if (ttScope.isOpen) {
3679
+ hide();
3680
+ }
3681
+ });
3523
3682
  }
3524
3683
 
3525
3684
  // Make sure tooltip is destroyed and removed.
3526
3685
  scope.$on('$destroy', function onDestroyTooltip() {
3527
- $timeout.cancel( transitionTimeout );
3528
- $timeout.cancel( popupTimeout );
3686
+ $timeout.cancel(transitionTimeout);
3687
+ $timeout.cancel(popupTimeout);
3688
+ $timeout.cancel(positionTimeout);
3529
3689
  unregisterTriggers();
3530
3690
  removeTooltip();
3531
3691
  ttScope = null;
@@ -3538,11 +3698,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
3538
3698
  })
3539
3699
 
3540
3700
  // This is mostly ngInclude code but with a custom scope
3541
- .directive( 'tooltipTemplateTransclude', [
3701
+ .directive('tooltipTemplateTransclude', [
3542
3702
  '$animate', '$sce', '$compile', '$templateRequest',
3543
3703
  function ($animate , $sce , $compile , $templateRequest) {
3544
3704
  return {
3545
- link: function ( scope, elem, attrs ) {
3705
+ link: function(scope, elem, attrs) {
3546
3706
  var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
3547
3707
 
3548
3708
  var changeCounter = 0,
@@ -3568,7 +3728,7 @@ function ($animate , $sce , $compile , $templateRequest) {
3568
3728
  }
3569
3729
  };
3570
3730
 
3571
- scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function (src) {
3731
+ scope.$watch($sce.parseAsResourceUrl(attrs.tooltipTemplateTransclude), function(src) {
3572
3732
  var thisChangeId = ++changeCounter;
3573
3733
 
3574
3734
  if (src) {
@@ -3610,10 +3770,10 @@ function ($animate , $sce , $compile , $templateRequest) {
3610
3770
  * They must not be animated as they're expected to be present on the tooltip on
3611
3771
  * initialization.
3612
3772
  */
3613
- .directive('tooltipClasses', function () {
3773
+ .directive('tooltipClasses', function() {
3614
3774
  return {
3615
3775
  restrict: 'A',
3616
- link: function (scope, element, attrs) {
3776
+ link: function(scope, element, attrs) {
3617
3777
  if (scope.placement) {
3618
3778
  element.addClass(scope.placement);
3619
3779
  }
@@ -3627,7 +3787,7 @@ function ($animate , $sce , $compile , $templateRequest) {
3627
3787
  };
3628
3788
  })
3629
3789
 
3630
- .directive( 'tooltipPopup', function () {
3790
+ .directive('tooltipPopup', function() {
3631
3791
  return {
3632
3792
  restrict: 'EA',
3633
3793
  replace: true,
@@ -3636,11 +3796,11 @@ function ($animate , $sce , $compile , $templateRequest) {
3636
3796
  };
3637
3797
  })
3638
3798
 
3639
- .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
3640
- return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
3799
+ .directive('tooltip', [ '$tooltip', function($tooltip) {
3800
+ return $tooltip('tooltip', 'tooltip', 'mouseenter');
3641
3801
  }])
3642
3802
 
3643
- .directive( 'tooltipTemplatePopup', function () {
3803
+ .directive('tooltipTemplatePopup', function() {
3644
3804
  return {
3645
3805
  restrict: 'EA',
3646
3806
  replace: true,
@@ -3650,13 +3810,13 @@ function ($animate , $sce , $compile , $templateRequest) {
3650
3810
  };
3651
3811
  })
3652
3812
 
3653
- .directive( 'tooltipTemplate', [ '$tooltip', function ( $tooltip ) {
3813
+ .directive('tooltipTemplate', ['$tooltip', function($tooltip) {
3654
3814
  return $tooltip('tooltipTemplate', 'tooltip', 'mouseenter', {
3655
3815
  useContentExp: true
3656
3816
  });
3657
3817
  }])
3658
3818
 
3659
- .directive( 'tooltipHtmlPopup', function () {
3819
+ .directive('tooltipHtmlPopup', function() {
3660
3820
  return {
3661
3821
  restrict: 'EA',
3662
3822
  replace: true,
@@ -3665,7 +3825,7 @@ function ($animate , $sce , $compile , $templateRequest) {
3665
3825
  };
3666
3826
  })
3667
3827
 
3668
- .directive( 'tooltipHtml', [ '$tooltip', function ( $tooltip ) {
3828
+ .directive('tooltipHtml', ['$tooltip', function($tooltip) {
3669
3829
  return $tooltip('tooltipHtml', 'tooltip', 'mouseenter', {
3670
3830
  useContentExp: true
3671
3831
  });
@@ -3674,7 +3834,7 @@ function ($animate , $sce , $compile , $templateRequest) {
3674
3834
  /*
3675
3835
  Deprecated
3676
3836
  */
3677
- .directive( 'tooltipHtmlUnsafePopup', function () {
3837
+ .directive('tooltipHtmlUnsafePopup', function() {
3678
3838
  return {
3679
3839
  restrict: 'EA',
3680
3840
  replace: true,
@@ -3684,13 +3844,13 @@ Deprecated
3684
3844
  })
3685
3845
 
3686
3846
  .value('tooltipHtmlUnsafeSuppressDeprecated', false)
3687
- .directive( 'tooltipHtmlUnsafe', [
3847
+ .directive('tooltipHtmlUnsafe', [
3688
3848
  '$tooltip', 'tooltipHtmlUnsafeSuppressDeprecated', '$log',
3689
- function ( $tooltip , tooltipHtmlUnsafeSuppressDeprecated , $log) {
3849
+ function($tooltip , tooltipHtmlUnsafeSuppressDeprecated , $log) {
3690
3850
  if (!tooltipHtmlUnsafeSuppressDeprecated) {
3691
3851
  $log.warn('tooltip-html-unsafe is now deprecated. Use tooltip-html or tooltip-template instead.');
3692
3852
  }
3693
- return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
3853
+ return $tooltip('tooltipHtmlUnsafe', 'tooltip', 'mouseenter');
3694
3854
  }]);
3695
3855
 
3696
3856
  /**
@@ -3698,9 +3858,9 @@ function ( $tooltip , tooltipHtmlUnsafeSuppressDeprecated , $log) {
3698
3858
  * function, placement as a function, inside, support for more triggers than
3699
3859
  * just mouse enter/leave, and selector delegatation.
3700
3860
  */
3701
- angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
3861
+ angular.module( 'ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
3702
3862
 
3703
- .directive( 'popoverTemplatePopup', function () {
3863
+ .directive('popoverTemplatePopup', function() {
3704
3864
  return {
3705
3865
  restrict: 'EA',
3706
3866
  replace: true,
@@ -3710,13 +3870,13 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
3710
3870
  };
3711
3871
  })
3712
3872
 
3713
- .directive( 'popoverTemplate', [ '$tooltip', function ( $tooltip ) {
3714
- return $tooltip( 'popoverTemplate', 'popover', 'click', {
3873
+ .directive('popoverTemplate', ['$tooltip', function($tooltip) {
3874
+ return $tooltip('popoverTemplate', 'popover', 'click', {
3715
3875
  useContentExp: true
3716
- } );
3876
+ });
3717
3877
  }])
3718
3878
 
3719
- .directive( 'popoverHtmlPopup', function () {
3879
+ .directive('popoverHtmlPopup', function() {
3720
3880
  return {
3721
3881
  restrict: 'EA',
3722
3882
  replace: true,
@@ -3725,13 +3885,13 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
3725
3885
  };
3726
3886
  })
3727
3887
 
3728
- .directive( 'popoverHtml', [ '$tooltip', function ( $tooltip ) {
3888
+ .directive('popoverHtml', ['$tooltip', function($tooltip) {
3729
3889
  return $tooltip( 'popoverHtml', 'popover', 'click', {
3730
3890
  useContentExp: true
3731
3891
  });
3732
3892
  }])
3733
3893
 
3734
- .directive( 'popoverPopup', function () {
3894
+ .directive('popoverPopup', function() {
3735
3895
  return {
3736
3896
  restrict: 'EA',
3737
3897
  replace: true,
@@ -3740,7 +3900,7 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
3740
3900
  };
3741
3901
  })
3742
3902
 
3743
- .directive( 'popover', [ '$tooltip', function ( $tooltip ) {
3903
+ .directive('popover', ['$tooltip', function($tooltip) {
3744
3904
  return $tooltip( 'popover', 'popover', 'click' );
3745
3905
  }]);
3746
3906
 
@@ -3751,104 +3911,144 @@ angular.module('ui.bootstrap.progressbar', [])
3751
3911
  max: 100
3752
3912
  })
3753
3913
 
3754
- .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
3755
- var self = this,
3756
- animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
3914
+ .value('$progressSuppressWarning', false)
3757
3915
 
3758
- this.bars = [];
3759
- $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
3916
+ .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
3917
+ var self = this,
3918
+ animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
3760
3919
 
3761
- this.addBar = function(bar, element) {
3762
- if ( !animate ) {
3763
- element.css({'transition': 'none'});
3764
- }
3920
+ this.bars = [];
3921
+ $scope.max = angular.isDefined($scope.max) ? $scope.max : progressConfig.max;
3765
3922
 
3766
- this.bars.push(bar);
3923
+ this.addBar = function(bar, element) {
3924
+ if (!animate) {
3925
+ element.css({'transition': 'none'});
3926
+ }
3767
3927
 
3768
- bar.max = $scope.max;
3928
+ this.bars.push(bar);
3769
3929
 
3770
- bar.$watch('value', function( value ) {
3771
- bar.recalculatePercentage();
3772
- });
3930
+ bar.max = $scope.max;
3773
3931
 
3774
- bar.recalculatePercentage = function() {
3775
- bar.percent = +(100 * bar.value / bar.max).toFixed(2);
3932
+ bar.$watch('value', function(value) {
3933
+ bar.recalculatePercentage();
3934
+ });
3776
3935
 
3777
- var totalPercentage = 0;
3778
- self.bars.forEach(function (bar) {
3779
- totalPercentage += bar.percent;
3780
- });
3936
+ bar.recalculatePercentage = function() {
3937
+ bar.percent = +(100 * bar.value / bar.max).toFixed(2);
3781
3938
 
3782
- if (totalPercentage > 100) {
3783
- bar.percent -= totalPercentage - 100;
3784
- }
3785
- };
3939
+ var totalPercentage = self.bars.reduce(function(total, bar) {
3940
+ return total + bar.percent;
3941
+ }, 0);
3786
3942
 
3787
- bar.$on('$destroy', function() {
3788
- element = null;
3789
- self.removeBar(bar);
3790
- });
3943
+ if (totalPercentage > 100) {
3944
+ bar.percent -= totalPercentage - 100;
3945
+ }
3791
3946
  };
3792
3947
 
3793
- this.removeBar = function(bar) {
3794
- this.bars.splice(this.bars.indexOf(bar), 1);
3795
- };
3948
+ bar.$on('$destroy', function() {
3949
+ element = null;
3950
+ self.removeBar(bar);
3951
+ });
3952
+ };
3796
3953
 
3797
- $scope.$watch('max', function(max) {
3798
- self.bars.forEach(function (bar) {
3799
- bar.max = $scope.max;
3800
- bar.recalculatePercentage();
3801
- });
3954
+ this.removeBar = function(bar) {
3955
+ this.bars.splice(this.bars.indexOf(bar), 1);
3956
+ };
3957
+
3958
+ $scope.$watch('max', function(max) {
3959
+ self.bars.forEach(function(bar) {
3960
+ bar.max = $scope.max;
3961
+ bar.recalculatePercentage();
3802
3962
  });
3963
+ });
3803
3964
  }])
3804
3965
 
3805
- .directive('progress', function() {
3806
- return {
3807
- restrict: 'EA',
3808
- replace: true,
3809
- transclude: true,
3810
- controller: 'ProgressController',
3811
- require: 'progress',
3812
- scope: {
3813
- max: '=?'
3814
- },
3815
- templateUrl: 'template/progressbar/progress.html'
3816
- };
3966
+ .directive('uibProgress', function() {
3967
+ return {
3968
+ restrict: 'EA',
3969
+ replace: true,
3970
+ transclude: true,
3971
+ controller: 'ProgressController',
3972
+ require: 'uibProgress',
3973
+ scope: {
3974
+ max: '=?'
3975
+ },
3976
+ templateUrl: 'template/progressbar/progress.html'
3977
+ };
3817
3978
  })
3818
3979
 
3819
- .directive('bar', function() {
3820
- return {
3821
- restrict: 'EA',
3822
- replace: true,
3823
- transclude: true,
3824
- require: '^progress',
3825
- scope: {
3826
- value: '=',
3827
- type: '@'
3828
- },
3829
- templateUrl: 'template/progressbar/bar.html',
3830
- link: function(scope, element, attrs, progressCtrl) {
3831
- progressCtrl.addBar(scope, element);
3832
- }
3833
- };
3980
+ .directive('progress', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
3981
+ return {
3982
+ restrict: 'EA',
3983
+ replace: true,
3984
+ transclude: true,
3985
+ controller: 'ProgressController',
3986
+ require: 'progress',
3987
+ scope: {
3988
+ max: '=?'
3989
+ },
3990
+ templateUrl: 'template/progressbar/progress.html',
3991
+ link: function() {
3992
+ if ($progressSuppressWarning) {
3993
+ $log.warn('progress is now deprecated. Use uib-progress instead');
3994
+ }
3995
+ }
3996
+ };
3997
+ }])
3998
+
3999
+ .directive('uibBar', function() {
4000
+ return {
4001
+ restrict: 'EA',
4002
+ replace: true,
4003
+ transclude: true,
4004
+ require: '^uibProgress',
4005
+ scope: {
4006
+ value: '=',
4007
+ type: '@'
4008
+ },
4009
+ templateUrl: 'template/progressbar/bar.html',
4010
+ link: function(scope, element, attrs, progressCtrl) {
4011
+ progressCtrl.addBar(scope, element);
4012
+ }
4013
+ };
3834
4014
  })
3835
4015
 
4016
+ .directive('bar', ['$log', '$progressSuppressWarning', function($log, $progressSuppressWarning) {
4017
+ return {
4018
+ restrict: 'EA',
4019
+ replace: true,
4020
+ transclude: true,
4021
+ require: '^progress',
4022
+ scope: {
4023
+ value: '=',
4024
+ type: '@'
4025
+ },
4026
+ templateUrl: 'template/progressbar/bar.html',
4027
+ link: function(scope, element, attrs, progressCtrl) {
4028
+ if ($progressSuppressWarning) {
4029
+ $log.warn('bar is now deprecated. Use uib-bar instead');
4030
+ }
4031
+ progressCtrl.addBar(scope, element);
4032
+ }
4033
+ };
4034
+ }])
4035
+
3836
4036
  .directive('progressbar', function() {
3837
- return {
3838
- restrict: 'EA',
3839
- replace: true,
3840
- transclude: true,
3841
- controller: 'ProgressController',
3842
- scope: {
3843
- value: '=',
3844
- max: '=?',
3845
- type: '@'
3846
- },
3847
- templateUrl: 'template/progressbar/progressbar.html',
3848
- link: function(scope, element, attrs, progressCtrl) {
3849
- progressCtrl.addBar(scope, angular.element(element.children()[0]));
3850
- }
3851
- };
4037
+ return {
4038
+ restrict: 'EA',
4039
+ replace: true,
4040
+ transclude: true,
4041
+ controller: 'ProgressController',
4042
+ scope: {
4043
+ value: '=',
4044
+ max: '=?',
4045
+ type: '@'
4046
+ },
4047
+ templateUrl: 'template/progressbar/progressbar.html',
4048
+ link: function(scope, element, attrs, progressCtrl) {
4049
+ progressCtrl.addBar(scope, angular.element(element.children()[0]));
4050
+ }
4051
+ };
3852
4052
  });
3853
4053
 
3854
4054
  angular.module('ui.bootstrap.rating', [])
@@ -3876,12 +4076,13 @@ angular.module('ui.bootstrap.rating', [])
3876
4076
 
3877
4077
  this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
3878
4078
  this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
3879
- var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
4079
+ var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles ;
3880
4080
  this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
3881
4081
  tmpTitles : ratingConfig.titles;
3882
-
3883
- var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) :
3884
- new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max );
4082
+
4083
+ var ratingStates = angular.isDefined($attrs.ratingStates) ?
4084
+ $scope.$parent.$eval($attrs.ratingStates) :
4085
+ new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
3885
4086
  $scope.range = this.buildTemplateObjects(ratingStates);
3886
4087
  };
3887
4088
 
@@ -3891,7 +4092,7 @@ angular.module('ui.bootstrap.rating', [])
3891
4092
  }
3892
4093
  return states;
3893
4094
  };
3894
-
4095
+
3895
4096
  this.getTitle = function(index) {
3896
4097
  if (index >= this.titles.length) {
3897
4098
  return index + 1;
@@ -3899,16 +4100,16 @@ angular.module('ui.bootstrap.rating', [])
3899
4100
  return this.titles[index];
3900
4101
  }
3901
4102
  };
3902
-
4103
+
3903
4104
  $scope.rate = function(value) {
3904
- if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) {
4105
+ if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
3905
4106
  ngModelCtrl.$setViewValue(ngModelCtrl.$viewValue === value ? 0 : value);
3906
4107
  ngModelCtrl.$render();
3907
4108
  }
3908
4109
  };
3909
4110
 
3910
4111
  $scope.enter = function(value) {
3911
- if ( !$scope.readonly ) {
4112
+ if (!$scope.readonly) {
3912
4113
  $scope.value = value;
3913
4114
  }
3914
4115
  $scope.onHover({value: value});
@@ -3923,7 +4124,7 @@ angular.module('ui.bootstrap.rating', [])
3923
4124
  if (/(37|38|39|40)/.test(evt.which)) {
3924
4125
  evt.preventDefault();
3925
4126
  evt.stopPropagation();
3926
- $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) );
4127
+ $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
3927
4128
  }
3928
4129
  };
3929
4130
 
@@ -3971,10 +4172,15 @@ angular.module('ui.bootstrap.tabs', [])
3971
4172
  if (tab.active && tab !== selectedTab) {
3972
4173
  tab.active = false;
3973
4174
  tab.onDeselect();
4175
+ selectedTab.selectCalled = false;
3974
4176
  }
3975
4177
  });
3976
4178
  selectedTab.active = true;
3977
- selectedTab.onSelect();
4179
+ // only call select if it has not already been called
4180
+ if (!selectedTab.selectCalled) {
4181
+ selectedTab.onSelect();
4182
+ selectedTab.selectCalled = true;
4183
+ }
3978
4184
  };
3979
4185
 
3980
4186
  ctrl.addTab = function addTab(tab) {
@@ -3985,8 +4191,7 @@ angular.module('ui.bootstrap.tabs', [])
3985
4191
  tab.active = true;
3986
4192
  } else if (tab.active) {
3987
4193
  ctrl.select(tab);
3988
- }
3989
- else {
4194
+ } else {
3990
4195
  tab.active = false;
3991
4196
  }
3992
4197
  };
@@ -4160,7 +4365,7 @@ angular.module('ui.bootstrap.tabs', [])
4160
4365
  });
4161
4366
 
4162
4367
  scope.disabled = false;
4163
- if ( attrs.disable ) {
4368
+ if (attrs.disable) {
4164
4369
  scope.$parent.$watch($parse(attrs.disable), function(value) {
4165
4370
  scope.disabled = !! value;
4166
4371
  });
@@ -4170,7 +4375,7 @@ angular.module('ui.bootstrap.tabs', [])
4170
4375
  // fix(tab): IE9 disabled attr renders grey text on enabled tab #2677
4171
4376
  // This code is duplicated from the lines above to make it easy to remove once
4172
4377
  // the feature has been completely deprecated
4173
- if ( attrs.disabled ) {
4378
+ if (attrs.disabled) {
4174
4379
  $log.warn('Use of "disabled" attribute has been deprecated, please use "disable"');
4175
4380
  scope.$parent.$watch($parse(attrs.disabled), function(value) {
4176
4381
  scope.disabled = !! value;
@@ -4178,7 +4383,7 @@ angular.module('ui.bootstrap.tabs', [])
4178
4383
  }
4179
4384
 
4180
4385
  scope.select = function() {
4181
- if ( !scope.disabled ) {
4386
+ if (!scope.disabled) {
4182
4387
  scope.active = true;
4183
4388
  }
4184
4389
  };
@@ -4195,7 +4400,7 @@ angular.module('ui.bootstrap.tabs', [])
4195
4400
  };
4196
4401
  }])
4197
4402
 
4198
- .directive('tabHeadingTransclude', [function() {
4403
+ .directive('tabHeadingTransclude', function() {
4199
4404
  return {
4200
4405
  restrict: 'A',
4201
4406
  require: '^tab',
@@ -4208,7 +4413,7 @@ angular.module('ui.bootstrap.tabs', [])
4208
4413
  });
4209
4414
  }
4210
4415
  };
4211
- }])
4416
+ })
4212
4417
 
4213
4418
  .directive('tabContentTransclude', function() {
4214
4419
  return {
@@ -4231,17 +4436,18 @@ angular.module('ui.bootstrap.tabs', [])
4231
4436
  });
4232
4437
  }
4233
4438
  };
4439
+
4234
4440
  function isTabHeading(node) {
4235
- return node.tagName && (
4441
+ return node.tagName && (
4236
4442
  node.hasAttribute('tab-heading') ||
4237
4443
  node.hasAttribute('data-tab-heading') ||
4444
+ node.hasAttribute('x-tab-heading') ||
4238
4445
  node.tagName.toLowerCase() === 'tab-heading' ||
4239
- node.tagName.toLowerCase() === 'data-tab-heading'
4446
+ node.tagName.toLowerCase() === 'data-tab-heading' ||
4447
+ node.tagName.toLowerCase() === 'x-tab-heading'
4240
4448
  );
4241
4449
  }
4242
- })
4243
-
4244
- ;
4450
+ });
4245
4451
 
4246
4452
  angular.module('ui.bootstrap.timepicker', [])
4247
4453
 
@@ -4261,29 +4467,29 @@ angular.module('ui.bootstrap.timepicker', [])
4261
4467
  ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
4262
4468
  meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
4263
4469
 
4264
- this.init = function( ngModelCtrl_, inputs ) {
4470
+ this.init = function(ngModelCtrl_, inputs) {
4265
4471
  ngModelCtrl = ngModelCtrl_;
4266
4472
  ngModelCtrl.$render = this.render;
4267
4473
 
4268
- ngModelCtrl.$formatters.unshift(function (modelValue) {
4269
- return modelValue ? new Date( modelValue ) : null;
4474
+ ngModelCtrl.$formatters.unshift(function(modelValue) {
4475
+ return modelValue ? new Date(modelValue) : null;
4270
4476
  });
4271
4477
 
4272
4478
  var hoursInputEl = inputs.eq(0),
4273
4479
  minutesInputEl = inputs.eq(1);
4274
4480
 
4275
4481
  var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
4276
- if ( mousewheel ) {
4277
- this.setupMousewheelEvents( hoursInputEl, minutesInputEl );
4482
+ if (mousewheel) {
4483
+ this.setupMousewheelEvents(hoursInputEl, minutesInputEl);
4278
4484
  }
4279
4485
 
4280
4486
  var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
4281
4487
  if (arrowkeys) {
4282
- this.setupArrowkeyEvents( hoursInputEl, minutesInputEl );
4488
+ this.setupArrowkeyEvents(hoursInputEl, minutesInputEl);
4283
4489
  }
4284
4490
 
4285
4491
  $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
4286
- this.setupInputEvents( hoursInputEl, minutesInputEl );
4492
+ this.setupInputEvents(hoursInputEl, minutesInputEl);
4287
4493
  };
4288
4494
 
4289
4495
  var hourStep = timepickerConfig.hourStep;
@@ -4319,7 +4525,7 @@ angular.module('ui.bootstrap.timepicker', [])
4319
4525
  };
4320
4526
 
4321
4527
  $scope.noDecrementHours = function() {
4322
- var decrementedSelected = addMinutes(selected, - hourStep * 60);
4528
+ var decrementedSelected = addMinutes(selected, -hourStep * 60);
4323
4529
  return decrementedSelected < min ||
4324
4530
  (decrementedSelected > selected && decrementedSelected > max);
4325
4531
  };
@@ -4331,7 +4537,7 @@ angular.module('ui.bootstrap.timepicker', [])
4331
4537
  };
4332
4538
 
4333
4539
  $scope.noDecrementMinutes = function() {
4334
- var decrementedSelected = addMinutes(selected, - minuteStep);
4540
+ var decrementedSelected = addMinutes(selected, -minuteStep);
4335
4541
  return decrementedSelected < min ||
4336
4542
  (decrementedSelected > selected && decrementedSelected > max);
4337
4543
  };
@@ -4340,7 +4546,7 @@ angular.module('ui.bootstrap.timepicker', [])
4340
4546
  if (selected.getHours() < 13) {
4341
4547
  return addMinutes(selected, 12 * 60) > max;
4342
4548
  } else {
4343
- return addMinutes(selected, - 12 * 60) < min;
4549
+ return addMinutes(selected, -12 * 60) < min;
4344
4550
  }
4345
4551
  };
4346
4552
 
@@ -4350,11 +4556,11 @@ angular.module('ui.bootstrap.timepicker', [])
4350
4556
  $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
4351
4557
  $scope.showMeridian = !!value;
4352
4558
 
4353
- if ( ngModelCtrl.$error.time ) {
4559
+ if (ngModelCtrl.$error.time) {
4354
4560
  // Evaluate from template
4355
4561
  var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
4356
- if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
4357
- selected.setHours( hours );
4562
+ if (angular.isDefined(hours) && angular.isDefined(minutes)) {
4563
+ selected.setHours(hours);
4358
4564
  refresh();
4359
4565
  }
4360
4566
  } else {
@@ -4364,18 +4570,18 @@ angular.module('ui.bootstrap.timepicker', [])
4364
4570
  }
4365
4571
 
4366
4572
  // Get $scope.hours in 24H mode if valid
4367
- function getHoursFromTemplate ( ) {
4368
- var hours = parseInt( $scope.hours, 10 );
4369
- var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
4370
- if ( !valid ) {
4573
+ function getHoursFromTemplate() {
4574
+ var hours = parseInt($scope.hours, 10);
4575
+ var valid = $scope.showMeridian ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
4576
+ if (!valid) {
4371
4577
  return undefined;
4372
4578
  }
4373
4579
 
4374
- if ( $scope.showMeridian ) {
4375
- if ( hours === 12 ) {
4580
+ if ($scope.showMeridian) {
4581
+ if (hours === 12) {
4376
4582
  hours = 0;
4377
4583
  }
4378
- if ( $scope.meridian === meridians[1] ) {
4584
+ if ($scope.meridian === meridians[1]) {
4379
4585
  hours = hours + 12;
4380
4586
  }
4381
4587
  }
@@ -4384,15 +4590,15 @@ angular.module('ui.bootstrap.timepicker', [])
4384
4590
 
4385
4591
  function getMinutesFromTemplate() {
4386
4592
  var minutes = parseInt($scope.minutes, 10);
4387
- return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
4593
+ return (minutes >= 0 && minutes < 60) ? minutes : undefined;
4388
4594
  }
4389
4595
 
4390
- function pad( value ) {
4391
- return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value.toString();
4596
+ function pad(value) {
4597
+ return (angular.isDefined(value) && value.toString().length < 2) ? '0' + value : value.toString();
4392
4598
  }
4393
4599
 
4394
4600
  // Respond on mousewheel spin
4395
- this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) {
4601
+ this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl) {
4396
4602
  var isScrollingUp = function(e) {
4397
4603
  if (e.originalEvent) {
4398
4604
  e = e.originalEvent;
@@ -4403,26 +4609,25 @@ angular.module('ui.bootstrap.timepicker', [])
4403
4609
  };
4404
4610
 
4405
4611
  hoursInputEl.bind('mousewheel wheel', function(e) {
4406
- $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() );
4612
+ $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
4407
4613
  e.preventDefault();
4408
4614
  });
4409
4615
 
4410
4616
  minutesInputEl.bind('mousewheel wheel', function(e) {
4411
- $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() );
4617
+ $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
4412
4618
  e.preventDefault();
4413
4619
  });
4414
4620
 
4415
4621
  };
4416
4622
 
4417
4623
  // Respond on up/down arrowkeys
4418
- this.setupArrowkeyEvents = function( hoursInputEl, minutesInputEl ) {
4624
+ this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl) {
4419
4625
  hoursInputEl.bind('keydown', function(e) {
4420
- if ( e.which === 38 ) { // up
4626
+ if (e.which === 38) { // up
4421
4627
  e.preventDefault();
4422
4628
  $scope.incrementHours();
4423
4629
  $scope.$apply();
4424
- }
4425
- else if ( e.which === 40 ) { // down
4630
+ } else if (e.which === 40) { // down
4426
4631
  e.preventDefault();
4427
4632
  $scope.decrementHours();
4428
4633
  $scope.$apply();
@@ -4430,12 +4635,11 @@ angular.module('ui.bootstrap.timepicker', [])
4430
4635
  });
4431
4636
 
4432
4637
  minutesInputEl.bind('keydown', function(e) {
4433
- if ( e.which === 38 ) { // up
4638
+ if (e.which === 38) { // up
4434
4639
  e.preventDefault();
4435
4640
  $scope.incrementMinutes();
4436
4641
  $scope.$apply();
4437
- }
4438
- else if ( e.which === 40 ) { // down
4642
+ } else if (e.which === 40) { // down
4439
4643
  e.preventDefault();
4440
4644
  $scope.decrementMinutes();
4441
4645
  $scope.$apply();
@@ -4443,15 +4647,15 @@ angular.module('ui.bootstrap.timepicker', [])
4443
4647
  });
4444
4648
  };
4445
4649
 
4446
- this.setupInputEvents = function( hoursInputEl, minutesInputEl ) {
4447
- if ( $scope.readonlyInput ) {
4650
+ this.setupInputEvents = function(hoursInputEl, minutesInputEl) {
4651
+ if ($scope.readonlyInput) {
4448
4652
  $scope.updateHours = angular.noop;
4449
4653
  $scope.updateMinutes = angular.noop;
4450
4654
  return;
4451
4655
  }
4452
4656
 
4453
4657
  var invalidate = function(invalidHours, invalidMinutes) {
4454
- ngModelCtrl.$setViewValue( null );
4658
+ ngModelCtrl.$setViewValue(null);
4455
4659
  ngModelCtrl.$setValidity('time', false);
4456
4660
  if (angular.isDefined(invalidHours)) {
4457
4661
  $scope.invalidHours = invalidHours;
@@ -4462,14 +4666,15 @@ angular.module('ui.bootstrap.timepicker', [])
4462
4666
  };
4463
4667
 
4464
4668
  $scope.updateHours = function() {
4465
- var hours = getHoursFromTemplate();
4669
+ var hours = getHoursFromTemplate(),
4670
+ minutes = getMinutesFromTemplate();
4466
4671
 
4467
- if ( angular.isDefined(hours) ) {
4468
- selected.setHours( hours );
4672
+ if (angular.isDefined(hours) && angular.isDefined(minutes)) {
4673
+ selected.setHours(hours);
4469
4674
  if (selected < min || selected > max) {
4470
4675
  invalidate(true);
4471
4676
  } else {
4472
- refresh( 'h' );
4677
+ refresh('h');
4473
4678
  }
4474
4679
  } else {
4475
4680
  invalidate(true);
@@ -4477,22 +4682,23 @@ angular.module('ui.bootstrap.timepicker', [])
4477
4682
  };
4478
4683
 
4479
4684
  hoursInputEl.bind('blur', function(e) {
4480
- if ( !$scope.invalidHours && $scope.hours < 10) {
4481
- $scope.$apply( function() {
4482
- $scope.hours = pad( $scope.hours );
4685
+ if (!$scope.invalidHours && $scope.hours < 10) {
4686
+ $scope.$apply(function() {
4687
+ $scope.hours = pad($scope.hours);
4483
4688
  });
4484
4689
  }
4485
4690
  });
4486
4691
 
4487
4692
  $scope.updateMinutes = function() {
4488
- var minutes = getMinutesFromTemplate();
4693
+ var minutes = getMinutesFromTemplate(),
4694
+ hours = getHoursFromTemplate();
4489
4695
 
4490
- if ( angular.isDefined(minutes) ) {
4491
- selected.setMinutes( minutes );
4696
+ if (angular.isDefined(minutes) && angular.isDefined(hours)) {
4697
+ selected.setMinutes(minutes);
4492
4698
  if (selected < min || selected > max) {
4493
4699
  invalidate(undefined, true);
4494
4700
  } else {
4495
- refresh( 'm' );
4701
+ refresh('m');
4496
4702
  }
4497
4703
  } else {
4498
4704
  invalidate(undefined, true);
@@ -4500,9 +4706,9 @@ angular.module('ui.bootstrap.timepicker', [])
4500
4706
  };
4501
4707
 
4502
4708
  minutesInputEl.bind('blur', function(e) {
4503
- if ( !$scope.invalidMinutes && $scope.minutes < 10 ) {
4504
- $scope.$apply( function() {
4505
- $scope.minutes = pad( $scope.minutes );
4709
+ if (!$scope.invalidMinutes && $scope.minutes < 10) {
4710
+ $scope.$apply(function() {
4711
+ $scope.minutes = pad($scope.minutes);
4506
4712
  });
4507
4713
  }
4508
4714
  });
@@ -4512,11 +4718,11 @@ angular.module('ui.bootstrap.timepicker', [])
4512
4718
  this.render = function() {
4513
4719
  var date = ngModelCtrl.$viewValue;
4514
4720
 
4515
- if ( isNaN(date) ) {
4721
+ if (isNaN(date)) {
4516
4722
  ngModelCtrl.$setValidity('time', false);
4517
4723
  $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.');
4518
4724
  } else {
4519
- if ( date ) {
4725
+ if (date) {
4520
4726
  selected = date;
4521
4727
  }
4522
4728
 
@@ -4532,10 +4738,10 @@ angular.module('ui.bootstrap.timepicker', [])
4532
4738
  };
4533
4739
 
4534
4740
  // Call internally when we know that model is valid.
4535
- function refresh( keyboardChange ) {
4741
+ function refresh(keyboardChange) {
4536
4742
  makeValid();
4537
- ngModelCtrl.$setViewValue( new Date(selected) );
4538
- updateTemplate( keyboardChange );
4743
+ ngModelCtrl.$setViewValue(new Date(selected));
4744
+ updateTemplate(keyboardChange);
4539
4745
  }
4540
4746
 
4541
4747
  function makeValid() {
@@ -4544,11 +4750,11 @@ angular.module('ui.bootstrap.timepicker', [])
4544
4750
  $scope.invalidMinutes = false;
4545
4751
  }
4546
4752
 
4547
- function updateTemplate( keyboardChange ) {
4753
+ function updateTemplate(keyboardChange) {
4548
4754
  var hours = selected.getHours(), minutes = selected.getMinutes();
4549
4755
 
4550
- if ( $scope.showMeridian ) {
4551
- hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
4756
+ if ($scope.showMeridian) {
4757
+ hours = (hours === 0 || hours === 12) ? 12 : hours % 12; // Convert 24 to 12 hour system
4552
4758
  }
4553
4759
 
4554
4760
  $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
@@ -4558,15 +4764,15 @@ angular.module('ui.bootstrap.timepicker', [])
4558
4764
  $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
4559
4765
  }
4560
4766
 
4561
- function addMinutes(date, minutes) {
4767
+ function addMinutes(date, minutes) {
4562
4768
  var dt = new Date(date.getTime() + minutes * 60000);
4563
4769
  var newDate = new Date(date);
4564
4770
  newDate.setHours(dt.getHours(), dt.getMinutes());
4565
4771
  return newDate;
4566
4772
  }
4567
4773
 
4568
- function addMinutesToSelected( minutes ) {
4569
- selected = addMinutes( selected, minutes );
4774
+ function addMinutesToSelected(minutes) {
4775
+ selected = addMinutes(selected, minutes);
4570
4776
  refresh();
4571
4777
  }
4572
4778
 
@@ -4578,21 +4784,25 @@ angular.module('ui.bootstrap.timepicker', [])
4578
4784
  addMinutesToSelected(hourStep * 60);
4579
4785
  }
4580
4786
  };
4787
+
4581
4788
  $scope.decrementHours = function() {
4582
4789
  if (!$scope.noDecrementHours()) {
4583
4790
  addMinutesToSelected(-hourStep * 60);
4584
4791
  }
4585
4792
  };
4793
+
4586
4794
  $scope.incrementMinutes = function() {
4587
4795
  if (!$scope.noIncrementMinutes()) {
4588
4796
  addMinutesToSelected(minuteStep);
4589
4797
  }
4590
4798
  };
4799
+
4591
4800
  $scope.decrementMinutes = function() {
4592
4801
  if (!$scope.noDecrementMinutes()) {
4593
4802
  addMinutesToSelected(-minuteStep);
4594
4803
  }
4595
4804
  };
4805
+
4596
4806
  $scope.toggleMeridian = function() {
4597
4807
  if (!$scope.noToggleMeridian()) {
4598
4808
  addMinutesToSelected(12 * 60 * (selected.getHours() < 12 ? 1 : -1));
@@ -4600,19 +4810,22 @@ angular.module('ui.bootstrap.timepicker', [])
4600
4810
  };
4601
4811
  }])
4602
4812
 
4603
- .directive('timepicker', function () {
4813
+ .directive('timepicker', function() {
4604
4814
  return {
4605
4815
  restrict: 'EA',
4606
4816
  require: ['timepicker', '?^ngModel'],
4607
4817
  controller:'TimepickerController',
4818
+ controllerAs: 'timepicker',
4608
4819
  replace: true,
4609
4820
  scope: {},
4610
- templateUrl: 'template/timepicker/timepicker.html',
4821
+ templateUrl: function(element, attrs) {
4822
+ return attrs.templateUrl || 'template/timepicker/timepicker.html';
4823
+ },
4611
4824
  link: function(scope, element, attrs, ctrls) {
4612
4825
  var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4613
4826
 
4614
- if ( ngModelCtrl ) {
4615
- timepickerCtrl.init( ngModelCtrl, element.find('input') );
4827
+ if (ngModelCtrl) {
4828
+ timepickerCtrl.init(ngModelCtrl, element.find('input'));
4616
4829
  }
4617
4830
  }
4618
4831
  };
@@ -4708,20 +4921,19 @@ function($q , $timeout , $rootScope , $log , $transitionSuppressDeprecated)
4708
4921
  return $transition;
4709
4922
  }]);
4710
4923
 
4711
- angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
4924
+ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
4712
4925
 
4713
4926
  /**
4714
4927
  * A helper service that can parse typeahead's syntax (string provided by users)
4715
4928
  * Extracted to a separate service for ease of unit testing
4716
4929
  */
4717
- .factory('typeaheadParser', ['$parse', function ($parse) {
4930
+ .factory('typeaheadParser', ['$parse', function($parse) {
4718
4931
 
4719
4932
  // 00000111000000000000022200000000000000003333333333333330000000000044000
4720
4933
  var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
4721
4934
 
4722
4935
  return {
4723
- parse:function (input) {
4724
-
4936
+ parse: function(input) {
4725
4937
  var match = input.match(TYPEAHEAD_REGEXP);
4726
4938
  if (!match) {
4727
4939
  throw new Error(
@@ -4740,461 +4952,469 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
4740
4952
  }])
4741
4953
 
4742
4954
  .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$position', 'typeaheadParser',
4743
- function ($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) {
4955
+ function($compile, $parse, $q, $timeout, $document, $window, $rootScope, $position, typeaheadParser) {
4956
+ var HOT_KEYS = [9, 13, 27, 38, 40];
4957
+ var eventDebounceTime = 200;
4744
4958
 
4745
- var HOT_KEYS = [9, 13, 27, 38, 40];
4746
- var eventDebounceTime = 200;
4747
-
4748
- return {
4749
- require:'ngModel',
4750
- link:function (originalScope, element, attrs, modelCtrl) {
4751
-
4752
- //SUPPORTED ATTRIBUTES (OPTIONS)
4753
-
4754
- //minimal no of characters that needs to be entered before typeahead kicks-in
4755
- var minLength = originalScope.$eval(attrs.typeaheadMinLength);
4756
- if (!minLength && minLength !== 0) {
4757
- minLength = 1;
4758
- }
4759
-
4760
- //minimal wait time after last character typed before typeahead kicks-in
4761
- var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
4959
+ return {
4960
+ require: ['ngModel', '^?ngModelOptions'],
4961
+ link: function(originalScope, element, attrs, ctrls) {
4962
+ var modelCtrl = ctrls[0];
4963
+ var ngModelOptions = ctrls[1];
4964
+ //SUPPORTED ATTRIBUTES (OPTIONS)
4965
+
4966
+ //minimal no of characters that needs to be entered before typeahead kicks-in
4967
+ var minLength = originalScope.$eval(attrs.typeaheadMinLength);
4968
+ if (!minLength && minLength !== 0) {
4969
+ minLength = 1;
4970
+ }
4762
4971
 
4763
- //should it restrict model values to the ones selected from the popup only?
4764
- var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
4972
+ //minimal wait time after last character typed before typeahead kicks-in
4973
+ var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
4765
4974
 
4766
- //binding to a variable that indicates if matches are being retrieved asynchronously
4767
- var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
4975
+ //should it restrict model values to the ones selected from the popup only?
4976
+ var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
4768
4977
 
4769
- //a callback executed when a match is selected
4770
- var onSelectCallback = $parse(attrs.typeaheadOnSelect);
4978
+ //binding to a variable that indicates if matches are being retrieved asynchronously
4979
+ var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
4771
4980
 
4772
- //should it select highlighted popup value when losing focus?
4773
- var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
4981
+ //a callback executed when a match is selected
4982
+ var onSelectCallback = $parse(attrs.typeaheadOnSelect);
4774
4983
 
4775
- //binding to a variable that indicates if there were no results after the query is completed
4776
- var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
4984
+ //should it select highlighted popup value when losing focus?
4985
+ var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
4777
4986
 
4778
- var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
4987
+ //binding to a variable that indicates if there were no results after the query is completed
4988
+ var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
4779
4989
 
4780
- var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
4990
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
4781
4991
 
4782
- var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
4992
+ var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
4783
4993
 
4784
- //If input matches an item of the list exactly, select it automatically
4785
- var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
4994
+ var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
4786
4995
 
4787
- //INTERNAL VARIABLES
4996
+ //If input matches an item of the list exactly, select it automatically
4997
+ var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
4788
4998
 
4789
- //model setter executed upon match selection
4790
- var $setModelValue = $parse(attrs.ngModel).assign;
4999
+ //INTERNAL VARIABLES
4791
5000
 
4792
- //expressions used by typeahead
4793
- var parserResult = typeaheadParser.parse(attrs.typeahead);
5001
+ //model setter executed upon match selection
5002
+ var parsedModel = $parse(attrs.ngModel);
5003
+ var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
5004
+ var $setModelValue = function(scope, newValue) {
5005
+ if (angular.isFunction(parsedModel(originalScope)) &&
5006
+ ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
5007
+ return invokeModelSetter(scope, {$$$p: newValue});
5008
+ } else {
5009
+ return parsedModel.assign(scope, newValue);
5010
+ }
5011
+ };
4794
5012
 
4795
- var hasFocus;
5013
+ //expressions used by typeahead
5014
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
4796
5015
 
4797
- //Used to avoid bug in iOS webview where iOS keyboard does not fire
4798
- //mousedown & mouseup events
4799
- //Issue #3699
4800
- var selected;
5016
+ var hasFocus;
4801
5017
 
4802
- //create a child scope for the typeahead directive so we are not polluting original scope
4803
- //with typeahead-specific data (matches, query etc.)
4804
- var scope = originalScope.$new();
4805
- originalScope.$on('$destroy', function(){
4806
- scope.$destroy();
4807
- });
5018
+ //Used to avoid bug in iOS webview where iOS keyboard does not fire
5019
+ //mousedown & mouseup events
5020
+ //Issue #3699
5021
+ var selected;
4808
5022
 
4809
- // WAI-ARIA
4810
- var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
4811
- element.attr({
4812
- 'aria-autocomplete': 'list',
4813
- 'aria-expanded': false,
4814
- 'aria-owns': popupId
4815
- });
5023
+ //create a child scope for the typeahead directive so we are not polluting original scope
5024
+ //with typeahead-specific data (matches, query etc.)
5025
+ var scope = originalScope.$new();
5026
+ var offDestroy = originalScope.$on('$destroy', function() {
5027
+ scope.$destroy();
5028
+ });
5029
+ scope.$on('$destroy', offDestroy);
5030
+
5031
+ // WAI-ARIA
5032
+ var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
5033
+ element.attr({
5034
+ 'aria-autocomplete': 'list',
5035
+ 'aria-expanded': false,
5036
+ 'aria-owns': popupId
5037
+ });
4816
5038
 
4817
- //pop-up element used to display matches
4818
- var popUpEl = angular.element('<div typeahead-popup></div>');
4819
- popUpEl.attr({
4820
- id: popupId,
4821
- matches: 'matches',
4822
- active: 'activeIdx',
4823
- select: 'select(activeIdx)',
4824
- 'move-in-progress': 'moveInProgress',
4825
- query: 'query',
4826
- position: 'position'
4827
- });
4828
- //custom item template
4829
- if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
4830
- popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
4831
- }
5039
+ //pop-up element used to display matches
5040
+ var popUpEl = angular.element('<div typeahead-popup></div>');
5041
+ popUpEl.attr({
5042
+ id: popupId,
5043
+ matches: 'matches',
5044
+ active: 'activeIdx',
5045
+ select: 'select(activeIdx)',
5046
+ 'move-in-progress': 'moveInProgress',
5047
+ query: 'query',
5048
+ position: 'position'
5049
+ });
5050
+ //custom item template
5051
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
5052
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
5053
+ }
4832
5054
 
4833
- var resetMatches = function() {
4834
- scope.matches = [];
4835
- scope.activeIdx = -1;
4836
- element.attr('aria-expanded', false);
4837
- };
5055
+ if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
5056
+ popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
5057
+ }
4838
5058
 
4839
- var getMatchId = function(index) {
4840
- return popupId + '-option-' + index;
4841
- };
5059
+ var resetMatches = function() {
5060
+ scope.matches = [];
5061
+ scope.activeIdx = -1;
5062
+ element.attr('aria-expanded', false);
5063
+ };
4842
5064
 
4843
- // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
4844
- // This attribute is added or removed automatically when the `activeIdx` changes.
4845
- scope.$watch('activeIdx', function(index) {
4846
- if (index < 0) {
4847
- element.removeAttr('aria-activedescendant');
4848
- } else {
4849
- element.attr('aria-activedescendant', getMatchId(index));
4850
- }
4851
- });
5065
+ var getMatchId = function(index) {
5066
+ return popupId + '-option-' + index;
5067
+ };
4852
5068
 
4853
- var inputIsExactMatch = function(inputValue, index) {
5069
+ // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
5070
+ // This attribute is added or removed automatically when the `activeIdx` changes.
5071
+ scope.$watch('activeIdx', function(index) {
5072
+ if (index < 0) {
5073
+ element.removeAttr('aria-activedescendant');
5074
+ } else {
5075
+ element.attr('aria-activedescendant', getMatchId(index));
5076
+ }
5077
+ });
4854
5078
 
4855
- if (scope.matches.length > index && inputValue) {
4856
- return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
4857
- }
5079
+ var inputIsExactMatch = function(inputValue, index) {
5080
+ if (scope.matches.length > index && inputValue) {
5081
+ return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
5082
+ }
4858
5083
 
4859
- return false;
4860
- };
5084
+ return false;
5085
+ };
4861
5086
 
4862
- var getMatchesAsync = function(inputValue) {
4863
-
4864
- var locals = {$viewValue: inputValue};
4865
- isLoadingSetter(originalScope, true);
4866
- isNoResultsSetter(originalScope, false);
4867
- $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
4868
-
4869
- //it might happen that several async queries were in progress if a user were typing fast
4870
- //but we are interested only in responses that correspond to the current view value
4871
- var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
4872
- if (onCurrentRequest && hasFocus) {
4873
- if (matches && matches.length > 0) {
4874
-
4875
- scope.activeIdx = focusFirst ? 0 : -1;
4876
- isNoResultsSetter(originalScope, false);
4877
- scope.matches.length = 0;
4878
-
4879
- //transform labels
4880
- for(var i=0; i<matches.length; i++) {
4881
- locals[parserResult.itemName] = matches[i];
4882
- scope.matches.push({
4883
- id: getMatchId(i),
4884
- label: parserResult.viewMapper(scope, locals),
4885
- model: matches[i]
4886
- });
4887
- }
5087
+ var getMatchesAsync = function(inputValue) {
5088
+ var locals = {$viewValue: inputValue};
5089
+ isLoadingSetter(originalScope, true);
5090
+ isNoResultsSetter(originalScope, false);
5091
+ $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
5092
+ //it might happen that several async queries were in progress if a user were typing fast
5093
+ //but we are interested only in responses that correspond to the current view value
5094
+ var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
5095
+ if (onCurrentRequest && hasFocus) {
5096
+ if (matches && matches.length > 0) {
5097
+
5098
+ scope.activeIdx = focusFirst ? 0 : -1;
5099
+ isNoResultsSetter(originalScope, false);
5100
+ scope.matches.length = 0;
5101
+
5102
+ //transform labels
5103
+ for (var i = 0; i < matches.length; i++) {
5104
+ locals[parserResult.itemName] = matches[i];
5105
+ scope.matches.push({
5106
+ id: getMatchId(i),
5107
+ label: parserResult.viewMapper(scope, locals),
5108
+ model: matches[i]
5109
+ });
5110
+ }
4888
5111
 
4889
- scope.query = inputValue;
4890
- //position pop-up with matches - we need to re-calculate its position each time we are opening a window
4891
- //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
4892
- //due to other elements being rendered
4893
- recalculatePosition();
5112
+ scope.query = inputValue;
5113
+ //position pop-up with matches - we need to re-calculate its position each time we are opening a window
5114
+ //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
5115
+ //due to other elements being rendered
5116
+ recalculatePosition();
4894
5117
 
4895
- element.attr('aria-expanded', true);
5118
+ element.attr('aria-expanded', true);
4896
5119
 
4897
- //Select the single remaining option if user input matches
4898
- if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
4899
- scope.select(0);
5120
+ //Select the single remaining option if user input matches
5121
+ if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
5122
+ scope.select(0);
5123
+ }
5124
+ } else {
5125
+ resetMatches();
5126
+ isNoResultsSetter(originalScope, true);
4900
5127
  }
4901
- } else {
4902
- resetMatches();
4903
- isNoResultsSetter(originalScope, true);
4904
5128
  }
4905
- }
4906
- if (onCurrentRequest) {
5129
+ if (onCurrentRequest) {
5130
+ isLoadingSetter(originalScope, false);
5131
+ }
5132
+ }, function() {
5133
+ resetMatches();
4907
5134
  isLoadingSetter(originalScope, false);
4908
- }
4909
- }, function(){
4910
- resetMatches();
4911
- isLoadingSetter(originalScope, false);
4912
- isNoResultsSetter(originalScope, true);
4913
- });
4914
- };
4915
-
4916
- // bind events only if appendToBody params exist - performance feature
4917
- if (appendToBody) {
4918
- angular.element($window).bind('resize', fireRecalculating);
4919
- $document.find('body').bind('scroll', fireRecalculating);
4920
- }
5135
+ isNoResultsSetter(originalScope, true);
5136
+ });
5137
+ };
4921
5138
 
4922
- // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
4923
- var timeoutEventPromise;
5139
+ // bind events only if appendToBody params exist - performance feature
5140
+ if (appendToBody) {
5141
+ angular.element($window).bind('resize', fireRecalculating);
5142
+ $document.find('body').bind('scroll', fireRecalculating);
5143
+ }
4924
5144
 
4925
- // Default progress type
4926
- scope.moveInProgress = false;
5145
+ // Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
5146
+ var timeoutEventPromise;
4927
5147
 
4928
- function fireRecalculating() {
4929
- if(!scope.moveInProgress){
4930
- scope.moveInProgress = true;
4931
- scope.$digest();
4932
- }
5148
+ // Default progress type
5149
+ scope.moveInProgress = false;
4933
5150
 
4934
- // Cancel previous timeout
4935
- if (timeoutEventPromise) {
4936
- $timeout.cancel(timeoutEventPromise);
4937
- }
5151
+ function fireRecalculating() {
5152
+ if (!scope.moveInProgress) {
5153
+ scope.moveInProgress = true;
5154
+ scope.$digest();
5155
+ }
4938
5156
 
4939
- // Debounced executing recalculate after events fired
4940
- timeoutEventPromise = $timeout(function () {
4941
- // if popup is visible
4942
- if (scope.matches.length) {
4943
- recalculatePosition();
5157
+ // Cancel previous timeout
5158
+ if (timeoutEventPromise) {
5159
+ $timeout.cancel(timeoutEventPromise);
4944
5160
  }
4945
5161
 
4946
- scope.moveInProgress = false;
4947
- scope.$digest();
4948
- }, eventDebounceTime);
4949
- }
5162
+ // Debounced executing recalculate after events fired
5163
+ timeoutEventPromise = $timeout(function() {
5164
+ // if popup is visible
5165
+ if (scope.matches.length) {
5166
+ recalculatePosition();
5167
+ }
4950
5168
 
4951
- // recalculate actual position and set new values to scope
4952
- // after digest loop is popup in right position
4953
- function recalculatePosition() {
4954
- scope.position = appendToBody ? $position.offset(element) : $position.position(element);
4955
- scope.position.top += element.prop('offsetHeight');
4956
- }
5169
+ scope.moveInProgress = false;
5170
+ scope.$digest();
5171
+ }, eventDebounceTime);
5172
+ }
4957
5173
 
4958
- resetMatches();
5174
+ // recalculate actual position and set new values to scope
5175
+ // after digest loop is popup in right position
5176
+ function recalculatePosition() {
5177
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
5178
+ scope.position.top += element.prop('offsetHeight');
5179
+ }
4959
5180
 
4960
- //we need to propagate user's query so we can higlight matches
4961
- scope.query = undefined;
5181
+ resetMatches();
4962
5182
 
4963
- //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
4964
- var timeoutPromise;
5183
+ //we need to propagate user's query so we can higlight matches
5184
+ scope.query = undefined;
4965
5185
 
4966
- var scheduleSearchWithTimeout = function(inputValue) {
4967
- timeoutPromise = $timeout(function () {
4968
- getMatchesAsync(inputValue);
4969
- }, waitTime);
4970
- };
5186
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
5187
+ var timeoutPromise;
4971
5188
 
4972
- var cancelPreviousTimeout = function() {
4973
- if (timeoutPromise) {
4974
- $timeout.cancel(timeoutPromise);
4975
- }
4976
- };
5189
+ var scheduleSearchWithTimeout = function(inputValue) {
5190
+ timeoutPromise = $timeout(function() {
5191
+ getMatchesAsync(inputValue);
5192
+ }, waitTime);
5193
+ };
4977
5194
 
4978
- //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
4979
- //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
4980
- modelCtrl.$parsers.unshift(function (inputValue) {
5195
+ var cancelPreviousTimeout = function() {
5196
+ if (timeoutPromise) {
5197
+ $timeout.cancel(timeoutPromise);
5198
+ }
5199
+ };
4981
5200
 
4982
- hasFocus = true;
5201
+ //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
5202
+ //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
5203
+ modelCtrl.$parsers.unshift(function(inputValue) {
5204
+ hasFocus = true;
4983
5205
 
4984
- if (minLength === 0 || inputValue && inputValue.length >= minLength) {
4985
- if (waitTime > 0) {
4986
- cancelPreviousTimeout();
4987
- scheduleSearchWithTimeout(inputValue);
5206
+ if (minLength === 0 || inputValue && inputValue.length >= minLength) {
5207
+ if (waitTime > 0) {
5208
+ cancelPreviousTimeout();
5209
+ scheduleSearchWithTimeout(inputValue);
5210
+ } else {
5211
+ getMatchesAsync(inputValue);
5212
+ }
4988
5213
  } else {
4989
- getMatchesAsync(inputValue);
5214
+ isLoadingSetter(originalScope, false);
5215
+ cancelPreviousTimeout();
5216
+ resetMatches();
4990
5217
  }
4991
- } else {
4992
- isLoadingSetter(originalScope, false);
4993
- cancelPreviousTimeout();
4994
- resetMatches();
4995
- }
4996
5218
 
4997
- if (isEditable) {
4998
- return inputValue;
4999
- } else {
5000
- if (!inputValue) {
5001
- // Reset in case user had typed something previously.
5002
- modelCtrl.$setValidity('editable', true);
5003
- return null;
5219
+ if (isEditable) {
5220
+ return inputValue;
5004
5221
  } else {
5005
- modelCtrl.$setValidity('editable', false);
5006
- return undefined;
5222
+ if (!inputValue) {
5223
+ // Reset in case user had typed something previously.
5224
+ modelCtrl.$setValidity('editable', true);
5225
+ return null;
5226
+ } else {
5227
+ modelCtrl.$setValidity('editable', false);
5228
+ return undefined;
5229
+ }
5007
5230
  }
5008
- }
5009
- });
5010
-
5011
- modelCtrl.$formatters.push(function (modelValue) {
5231
+ });
5012
5232
 
5013
- var candidateViewValue, emptyViewValue;
5014
- var locals = {};
5233
+ modelCtrl.$formatters.push(function(modelValue) {
5234
+ var candidateViewValue, emptyViewValue;
5235
+ var locals = {};
5015
5236
 
5016
- // The validity may be set to false via $parsers (see above) if
5017
- // the model is restricted to selected values. If the model
5018
- // is set manually it is considered to be valid.
5019
- if (!isEditable) {
5020
- modelCtrl.$setValidity('editable', true);
5021
- }
5022
-
5023
- if (inputFormatter) {
5237
+ // The validity may be set to false via $parsers (see above) if
5238
+ // the model is restricted to selected values. If the model
5239
+ // is set manually it is considered to be valid.
5240
+ if (!isEditable) {
5241
+ modelCtrl.$setValidity('editable', true);
5242
+ }
5024
5243
 
5025
- locals.$model = modelValue;
5026
- return inputFormatter(originalScope, locals);
5244
+ if (inputFormatter) {
5245
+ locals.$model = modelValue;
5246
+ return inputFormatter(originalScope, locals);
5247
+ } else {
5248
+ //it might happen that we don't have enough info to properly render input value
5249
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
5250
+ locals[parserResult.itemName] = modelValue;
5251
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
5252
+ locals[parserResult.itemName] = undefined;
5253
+ emptyViewValue = parserResult.viewMapper(originalScope, locals);
5254
+
5255
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
5256
+ }
5257
+ });
5027
5258
 
5028
- } else {
5259
+ scope.select = function(activeIdx) {
5260
+ //called from within the $digest() cycle
5261
+ var locals = {};
5262
+ var model, item;
5029
5263
 
5030
- //it might happen that we don't have enough info to properly render input value
5031
- //we need to check for this situation and simply return model value if we can't apply custom formatting
5032
- locals[parserResult.itemName] = modelValue;
5033
- candidateViewValue = parserResult.viewMapper(originalScope, locals);
5034
- locals[parserResult.itemName] = undefined;
5035
- emptyViewValue = parserResult.viewMapper(originalScope, locals);
5264
+ selected = true;
5265
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
5266
+ model = parserResult.modelMapper(originalScope, locals);
5267
+ $setModelValue(originalScope, model);
5268
+ modelCtrl.$setValidity('editable', true);
5269
+ modelCtrl.$setValidity('parse', true);
5036
5270
 
5037
- return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
5038
- }
5039
- });
5271
+ onSelectCallback(originalScope, {
5272
+ $item: item,
5273
+ $model: model,
5274
+ $label: parserResult.viewMapper(originalScope, locals)
5275
+ });
5040
5276
 
5041
- scope.select = function (activeIdx) {
5042
- //called from within the $digest() cycle
5043
- var locals = {};
5044
- var model, item;
5045
-
5046
- selected = true;
5047
- locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
5048
- model = parserResult.modelMapper(originalScope, locals);
5049
- $setModelValue(originalScope, model);
5050
- modelCtrl.$setValidity('editable', true);
5051
- modelCtrl.$setValidity('parse', true);
5052
-
5053
- onSelectCallback(originalScope, {
5054
- $item: item,
5055
- $model: model,
5056
- $label: parserResult.viewMapper(originalScope, locals)
5057
- });
5277
+ resetMatches();
5058
5278
 
5059
- resetMatches();
5279
+ //return focus to the input element if a match was selected via a mouse click event
5280
+ // use timeout to avoid $rootScope:inprog error
5281
+ if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
5282
+ $timeout(function() { element[0].focus(); }, 0, false);
5283
+ }
5284
+ };
5060
5285
 
5061
- //return focus to the input element if a match was selected via a mouse click event
5062
- // use timeout to avoid $rootScope:inprog error
5063
- $timeout(function() { element[0].focus(); }, 0, false);
5064
- };
5286
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
5287
+ element.bind('keydown', function(evt) {
5288
+ //typeahead is open and an "interesting" key was pressed
5289
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
5290
+ return;
5291
+ }
5065
5292
 
5066
- //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
5067
- element.bind('keydown', function (evt) {
5293
+ // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
5294
+ if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
5295
+ resetMatches();
5296
+ scope.$digest();
5297
+ return;
5298
+ }
5068
5299
 
5069
- //typeahead is open and an "interesting" key was pressed
5070
- if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
5071
- return;
5072
- }
5300
+ evt.preventDefault();
5073
5301
 
5074
- // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
5075
- if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
5076
- resetMatches();
5077
- scope.$digest();
5078
- return;
5079
- }
5302
+ if (evt.which === 40) {
5303
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
5304
+ scope.$digest();
5080
5305
 
5081
- evt.preventDefault();
5306
+ } else if (evt.which === 38) {
5307
+ scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
5308
+ scope.$digest();
5082
5309
 
5083
- if (evt.which === 40) {
5084
- scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
5085
- scope.$digest();
5310
+ } else if (evt.which === 13 || evt.which === 9) {
5311
+ scope.$apply(function () {
5312
+ scope.select(scope.activeIdx);
5313
+ });
5086
5314
 
5087
- } else if (evt.which === 38) {
5088
- scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
5089
- scope.$digest();
5315
+ } else if (evt.which === 27) {
5316
+ evt.stopPropagation();
5090
5317
 
5091
- } else if (evt.which === 13 || evt.which === 9) {
5092
- scope.$apply(function () {
5093
- scope.select(scope.activeIdx);
5094
- });
5318
+ resetMatches();
5319
+ scope.$digest();
5320
+ }
5321
+ });
5095
5322
 
5096
- } else if (evt.which === 27) {
5097
- evt.stopPropagation();
5323
+ element.bind('blur', function() {
5324
+ if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
5325
+ selected = true;
5326
+ scope.$apply(function() {
5327
+ scope.select(scope.activeIdx);
5328
+ });
5329
+ }
5330
+ hasFocus = false;
5331
+ selected = false;
5332
+ });
5098
5333
 
5099
- resetMatches();
5100
- scope.$digest();
5101
- }
5102
- });
5334
+ // Keep reference to click handler to unbind it.
5335
+ var dismissClickHandler = function(evt) {
5336
+ // Issue #3973
5337
+ // Firefox treats right click as a click on document
5338
+ if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
5339
+ resetMatches();
5340
+ if (!$rootScope.$$phase) {
5341
+ scope.$digest();
5342
+ }
5343
+ }
5344
+ };
5103
5345
 
5104
- element.bind('blur', function () {
5105
- if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
5106
- selected = true;
5107
- scope.$apply(function () {
5108
- scope.select(scope.activeIdx);
5109
- });
5110
- }
5111
- hasFocus = false;
5112
- selected = false;
5113
- });
5346
+ $document.bind('click', dismissClickHandler);
5114
5347
 
5115
- // Keep reference to click handler to unbind it.
5116
- var dismissClickHandler = function (evt) {
5117
- // Issue #3973
5118
- // Firefox treats right click as a click on document
5119
- if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
5120
- resetMatches();
5121
- if (!$rootScope.$$phase) {
5122
- scope.$digest();
5348
+ originalScope.$on('$destroy', function() {
5349
+ $document.unbind('click', dismissClickHandler);
5350
+ if (appendToBody) {
5351
+ $popup.remove();
5123
5352
  }
5124
- }
5125
- };
5353
+ // Prevent jQuery cache memory leak
5354
+ popUpEl.remove();
5355
+ });
5126
5356
 
5127
- $document.bind('click', dismissClickHandler);
5357
+ var $popup = $compile(popUpEl)(scope);
5128
5358
 
5129
- originalScope.$on('$destroy', function(){
5130
- $document.unbind('click', dismissClickHandler);
5131
5359
  if (appendToBody) {
5132
- $popup.remove();
5360
+ $document.find('body').append($popup);
5361
+ } else {
5362
+ element.after($popup);
5133
5363
  }
5134
- // Prevent jQuery cache memory leak
5135
- popUpEl.remove();
5136
- });
5137
-
5138
- var $popup = $compile(popUpEl)(scope);
5139
-
5140
- if (appendToBody) {
5141
- $document.find('body').append($popup);
5142
- } else {
5143
- element.after($popup);
5144
5364
  }
5145
- }
5146
- };
5365
+ };
5147
5366
 
5148
- }])
5367
+ }])
5149
5368
 
5150
- .directive('typeaheadPopup', function () {
5369
+ .directive('typeaheadPopup', function() {
5151
5370
  return {
5152
- restrict:'EA',
5153
- scope:{
5154
- matches:'=',
5155
- query:'=',
5156
- active:'=',
5157
- position:'&',
5158
- moveInProgress:'=',
5159
- select:'&'
5371
+ restrict: 'EA',
5372
+ scope: {
5373
+ matches: '=',
5374
+ query: '=',
5375
+ active: '=',
5376
+ position: '&',
5377
+ moveInProgress: '=',
5378
+ select: '&'
5160
5379
  },
5161
- replace:true,
5162
- templateUrl:'template/typeahead/typeahead-popup.html',
5163
- link:function (scope, element, attrs) {
5164
-
5380
+ replace: true,
5381
+ templateUrl: function(element, attrs) {
5382
+ return attrs.popupTemplateUrl || 'template/typeahead/typeahead-popup.html';
5383
+ },
5384
+ link: function(scope, element, attrs) {
5165
5385
  scope.templateUrl = attrs.templateUrl;
5166
5386
 
5167
- scope.isOpen = function () {
5387
+ scope.isOpen = function() {
5168
5388
  return scope.matches.length > 0;
5169
5389
  };
5170
5390
 
5171
- scope.isActive = function (matchIdx) {
5391
+ scope.isActive = function(matchIdx) {
5172
5392
  return scope.active == matchIdx;
5173
5393
  };
5174
5394
 
5175
- scope.selectActive = function (matchIdx) {
5395
+ scope.selectActive = function(matchIdx) {
5176
5396
  scope.active = matchIdx;
5177
5397
  };
5178
5398
 
5179
- scope.selectMatch = function (activeIdx) {
5399
+ scope.selectMatch = function(activeIdx) {
5180
5400
  scope.select({activeIdx:activeIdx});
5181
5401
  };
5182
5402
  }
5183
5403
  };
5184
5404
  })
5185
5405
 
5186
- .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function ($templateRequest, $compile, $parse) {
5406
+ .directive('typeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
5187
5407
  return {
5188
- restrict:'EA',
5189
- scope:{
5190
- index:'=',
5191
- match:'=',
5192
- query:'='
5408
+ restrict: 'EA',
5409
+ scope: {
5410
+ index: '=',
5411
+ match: '=',
5412
+ query: '='
5193
5413
  },
5194
- link:function (scope, element, attrs) {
5414
+ link:function(scope, element, attrs) {
5195
5415
  var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
5196
5416
  $templateRequest(tplUrl).then(function(tplContent) {
5197
- $compile(tplContent.trim())(scope, function(clonedElement){
5417
+ $compile(tplContent.trim())(scope, function(clonedElement) {
5198
5418
  element.replaceWith(clonedElement);
5199
5419
  });
5200
5420
  });
@@ -5202,14 +5422,29 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
5202
5422
  };
5203
5423
  }])
5204
5424
 
5205
- .filter('typeaheadHighlight', function() {
5425
+ .filter('typeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
5426
+ var isSanitizePresent;
5427
+ isSanitizePresent = $injector.has('$sanitize');
5206
5428
 
5207
5429
  function escapeRegexp(queryToEscape) {
5430
+ // Regex: capture the whole query string and replace it with the string that will be used to match
5431
+ // the results, for example if the capture is "a" the result will be \a
5208
5432
  return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
5209
5433
  }
5210
5434
 
5435
+ function containsHtml(matchItem) {
5436
+ return /<.*>/g.test(matchItem);
5437
+ }
5438
+
5211
5439
  return function(matchItem, query) {
5212
- return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
5440
+ if (!isSanitizePresent && containsHtml(matchItem)) {
5441
+ $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
5442
+ }
5443
+ 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
5444
+ if (!isSanitizePresent) {
5445
+ matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
5446
+ }
5447
+ return matchItem;
5213
5448
  };
5214
- });
5215
- !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>');
5449
+ }]);
5450
+ !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>');