angular-ui-bootstrap-rails 0.13.3 → 0.13.4

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