angular-ui-bootstrap-rails 0.13.3 → 0.13.4

Sign up to get free protection for your applications and to get access to all the features.
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>');