angular-ui-bootstrap-rails 0.10.0 → 0.11.0

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: 1f8242f5fc7aaf064a61d57097ccd63d647a31f7
4
- data.tar.gz: 0bccd68709856ae1558baff6579b6ce3f36bb027
3
+ metadata.gz: 85f812e3f7f178e10580078d59e6a10ec3a972d8
4
+ data.tar.gz: 13e0843c45c3a3f4e49caebe5810d734f24a69c6
5
5
  SHA512:
6
- metadata.gz: 11d5e42f80be112f83902749ac98539576a67c4cc858db7c06ccfb64855166ea0e41741df03dc3676394f2ed08edc3cceb814a2964275ca36a285ffa8eb41492
7
- data.tar.gz: 63d5dd56b1f5ddf4723dbb4a2d0f88f2a00f5f570a39ba92dde9d15571193cb036b57570550b81353c231b3113edd4b2795980cfa8f24a0067d420e1d57d4f6d
6
+ metadata.gz: 4259ed0ada0ea8c87aade0067cbfc7c85440eab8e13b84a81c950798ef8002148807114d96a2cf7215bc6ac5a2a94f9f339ac3e36ab77420935cf41aa2344640
7
+ data.tar.gz: 87a81f50fb592b85e7be03e413abdfd32bc875751e505ade49c5dbdcdf999990740c52a2ac5a399c0f9247fbd03c05ff4abd2ddd4646e3c3f57f8ceca20e4244
@@ -1,7 +1,7 @@
1
1
  module AngularUI
2
2
  module Bootstrap
3
3
  module Rails
4
- VERSION = "0.10.0"
4
+ VERSION = "0.11.0"
5
5
  end
6
6
  end
7
7
  end
@@ -2,11 +2,11 @@
2
2
  * angular-ui-bootstrap
3
3
  * http://angular-ui.github.io/bootstrap/
4
4
 
5
- * Version: 0.10.0 - 2014-01-13
5
+ * Version: 0.11.0 - 2014-05-01
6
6
  * License: MIT
7
7
  */
8
- angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
9
- angular.module("ui.bootstrap.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/popup.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.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"]);
8
+ angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.bindHtml","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dropdown","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
9
+ angular.module("ui.bootstrap.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-unsafe-popup.html","template/tooltip/tooltip-popup.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.transition', [])
11
11
 
12
12
  /**
@@ -23,7 +23,7 @@ angular.module('ui.bootstrap.transition', [])
23
23
  var $transition = function(element, trigger, options) {
24
24
  options = options || {};
25
25
  var deferred = $q.defer();
26
- var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
26
+ var endEventName = $transition[options.animation ? 'animationEndEventName' : 'transitionEndEventName'];
27
27
 
28
28
  var transitionEndHandler = function(event) {
29
29
  $rootScope.$apply(function() {
@@ -92,7 +92,7 @@ angular.module('ui.bootstrap.transition', [])
92
92
 
93
93
  angular.module('ui.bootstrap.collapse', ['ui.bootstrap.transition'])
94
94
 
95
- .directive('collapse', ['$transition', function ($transition, $timeout) {
95
+ .directive('collapse', ['$transition', function ($transition) {
96
96
 
97
97
  return {
98
98
  link: function (scope, element, attrs) {
@@ -188,7 +188,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
188
188
  });
189
189
  }
190
190
  };
191
-
191
+
192
192
  // This is called from the accordion-group directive to add itself to the accordion
193
193
  this.addGroup = function(groupScope) {
194
194
  var that = this;
@@ -203,7 +203,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
203
203
  this.removeGroup = function(group) {
204
204
  var index = this.groups.indexOf(group);
205
205
  if ( index !== -1 ) {
206
- this.groups.splice(this.groups.indexOf(group), 1);
206
+ this.groups.splice(index, 1);
207
207
  }
208
208
  };
209
209
 
@@ -222,46 +222,40 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
222
222
  })
223
223
 
224
224
  // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
225
- .directive('accordionGroup', ['$parse', function($parse) {
225
+ .directive('accordionGroup', function() {
226
226
  return {
227
227
  require:'^accordion', // We need this directive to be inside an accordion
228
228
  restrict:'EA',
229
229
  transclude:true, // It transcludes the contents of the directive into the template
230
230
  replace: true, // The element containing the directive will be replaced with the template
231
231
  templateUrl:'template/accordion/accordion-group.html',
232
- scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
232
+ scope: {
233
+ heading: '@', // Interpolate the heading attribute onto this scope
234
+ isOpen: '=?',
235
+ isDisabled: '=?'
236
+ },
233
237
  controller: function() {
234
238
  this.setHeading = function(element) {
235
239
  this.heading = element;
236
240
  };
237
241
  },
238
242
  link: function(scope, element, attrs, accordionCtrl) {
239
- var getIsOpen, setIsOpen;
240
-
241
243
  accordionCtrl.addGroup(scope);
242
244
 
243
- scope.isOpen = false;
244
-
245
- if ( attrs.isOpen ) {
246
- getIsOpen = $parse(attrs.isOpen);
247
- setIsOpen = getIsOpen.assign;
248
-
249
- scope.$parent.$watch(getIsOpen, function(value) {
250
- scope.isOpen = !!value;
251
- });
252
- }
253
-
254
245
  scope.$watch('isOpen', function(value) {
255
246
  if ( value ) {
256
247
  accordionCtrl.closeOthers(scope);
257
248
  }
258
- if ( setIsOpen ) {
259
- setIsOpen(scope.$parent, value);
260
- }
261
249
  });
250
+
251
+ scope.toggleOpen = function() {
252
+ if ( !scope.isDisabled ) {
253
+ scope.isOpen = !scope.isOpen;
254
+ }
255
+ };
262
256
  }
263
257
  };
264
- }])
258
+ })
265
259
 
266
260
  // Use accordion-heading below an accordion-group to provide a heading containing HTML
267
261
  // <accordion-group>
@@ -274,13 +268,11 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
274
268
  template: '', // In effect remove this element!
275
269
  replace: true,
276
270
  require: '^accordionGroup',
277
- compile: function(element, attr, transclude) {
278
- return function link(scope, element, attr, accordionGroupCtrl) {
279
- // Pass the heading to the accordion-group controller
280
- // so that it can be transcluded into the right place in the template
281
- // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
282
- accordionGroupCtrl.setHeading(transclude(scope, function() {}));
283
- };
271
+ link: function(scope, element, attr, accordionGroupCtrl, transclude) {
272
+ // Pass the heading to the accordion-group controller
273
+ // so that it can be transcluded into the right place in the template
274
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
275
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
284
276
  }
285
277
  };
286
278
  })
@@ -305,7 +297,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
305
297
  };
306
298
  });
307
299
 
308
- angular.module("ui.bootstrap.alert", [])
300
+ angular.module('ui.bootstrap.alert', [])
309
301
 
310
302
  .controller('AlertController', ['$scope', '$attrs', function ($scope, $attrs) {
311
303
  $scope.closeable = 'close' in $attrs;
@@ -319,7 +311,7 @@ angular.module("ui.bootstrap.alert", [])
319
311
  transclude:true,
320
312
  replace:true,
321
313
  scope: {
322
- type: '=',
314
+ type: '@',
323
315
  close: '&'
324
316
  }
325
317
  };
@@ -361,9 +353,11 @@ angular.module('ui.bootstrap.buttons', [])
361
353
 
362
354
  //ui->model
363
355
  element.bind(buttonsCtrl.toggleEvent, function () {
364
- if (!element.hasClass(buttonsCtrl.activeClass)) {
356
+ var isActive = element.hasClass(buttonsCtrl.activeClass);
357
+
358
+ if (!isActive || angular.isDefined(attrs.uncheckable)) {
365
359
  scope.$apply(function () {
366
- ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
360
+ ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.btnRadio));
367
361
  ngModelCtrl.$render();
368
362
  });
369
363
  }
@@ -386,7 +380,7 @@ angular.module('ui.bootstrap.buttons', [])
386
380
  function getFalseValue() {
387
381
  return getCheckboxValue(attrs.btnCheckboxFalse, false);
388
382
  }
389
-
383
+
390
384
  function getCheckboxValue(attributeValue, defaultValue) {
391
385
  var val = scope.$eval(attributeValue);
392
386
  return angular.isDefined(val) ? val : defaultValue;
@@ -417,20 +411,20 @@ angular.module('ui.bootstrap.buttons', [])
417
411
  *
418
412
  */
419
413
  angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
420
- .controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
414
+ .controller('CarouselController', ['$scope', '$timeout', '$transition', function ($scope, $timeout, $transition) {
421
415
  var self = this,
422
- slides = self.slides = [],
416
+ slides = self.slides = $scope.slides = [],
423
417
  currentIndex = -1,
424
418
  currentTimeout, isPlaying;
425
419
  self.currentSlide = null;
426
420
 
427
421
  var destroyed = false;
428
422
  /* direction: "prev" or "next" */
429
- self.select = function(nextSlide, direction) {
423
+ self.select = $scope.select = function(nextSlide, direction) {
430
424
  var nextIndex = slides.indexOf(nextSlide);
431
425
  //Decide direction if it's not given
432
426
  if (direction === undefined) {
433
- direction = nextIndex > currentIndex ? "next" : "prev";
427
+ direction = nextIndex > currentIndex ? 'next' : 'prev';
434
428
  }
435
429
  if (nextSlide && nextSlide !== self.currentSlide) {
436
430
  if ($scope.$currentTransition) {
@@ -506,18 +500,10 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
506
500
  }
507
501
  };
508
502
 
509
- $scope.select = function(slide) {
510
- self.select(slide);
511
- };
512
-
513
503
  $scope.isActive = function(slide) {
514
504
  return self.currentSlide === slide;
515
505
  };
516
506
 
517
- $scope.slides = function() {
518
- return slides;
519
- };
520
-
521
507
  $scope.$watch('interval', restartTimer);
522
508
  $scope.$on('$destroy', resetTimer);
523
509
 
@@ -666,36 +652,13 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
666
652
  </div>
667
653
  </slide>
668
654
  </carousel>
669
- <div class="row-fluid">
670
- <div class="span6">
671
- <ul>
672
- <li ng-repeat="slide in slides">
673
- <button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
674
- {{$index}}: {{slide.text}}
675
- </li>
676
- </ul>
677
- <a class="btn" ng-click="addSlide()">Add Slide</a>
678
- </div>
679
- <div class="span6">
680
- Interval, in milliseconds: <input type="number" ng-model="myInterval">
681
- <br />Enter a negative number to stop the interval.
682
- </div>
683
- </div>
655
+ Interval, in milliseconds: <input type="number" ng-model="myInterval">
656
+ <br />Enter a negative number to stop the interval.
684
657
  </div>
685
658
  </file>
686
659
  <file name="script.js">
687
660
  function CarouselDemoCtrl($scope) {
688
661
  $scope.myInterval = 5000;
689
- var slides = $scope.slides = [];
690
- $scope.addSlide = function() {
691
- var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
692
- slides.push({
693
- image: 'http://placekitten.com/' + newWidth + '/200',
694
- text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
695
- ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
696
- });
697
- };
698
- for (var i=0; i<4; i++) $scope.addSlide();
699
662
  }
700
663
  </file>
701
664
  <file name="demo.css">
@@ -707,7 +670,7 @@ function CarouselDemoCtrl($scope) {
707
670
  </example>
708
671
  */
709
672
 
710
- .directive('slide', ['$parse', function($parse) {
673
+ .directive('slide', function() {
711
674
  return {
712
675
  require: '^carousel',
713
676
  restrict: 'EA',
@@ -715,30 +678,9 @@ function CarouselDemoCtrl($scope) {
715
678
  replace: true,
716
679
  templateUrl: 'template/carousel/slide.html',
717
680
  scope: {
681
+ active: '=?'
718
682
  },
719
683
  link: function (scope, element, attrs, carouselCtrl) {
720
- //Set up optional 'active' = binding
721
- if (attrs.active) {
722
- var getActive = $parse(attrs.active);
723
- var setActive = getActive.assign;
724
- var lastValue = scope.active = getActive(scope.$parent);
725
- scope.$watch(function parentActiveWatch() {
726
- var parentActive = getActive(scope.$parent);
727
-
728
- if (parentActive !== scope.active) {
729
- // we are out of sync and need to copy
730
- if (parentActive !== lastValue) {
731
- // parent changed and it has precedence
732
- lastValue = scope.active = parentActive;
733
- } else {
734
- // if the parent can be assigned then do so
735
- setActive(scope.$parent, parentActive = lastValue = scope.active);
736
- }
737
- }
738
- return parentActive;
739
- });
740
- }
741
-
742
684
  carouselCtrl.addSlide(scope, element);
743
685
  //when the scope is destroyed then remove the slide from the current slides array
744
686
  scope.$on('$destroy', function() {
@@ -752,6 +694,133 @@ function CarouselDemoCtrl($scope) {
752
694
  });
753
695
  }
754
696
  };
697
+ });
698
+
699
+ angular.module('ui.bootstrap.dateparser', [])
700
+
701
+ .service('dateParser', ['$locale', 'orderByFilter', function($locale, orderByFilter) {
702
+
703
+ this.parsers = {};
704
+
705
+ var formatCodeToRegex = {
706
+ 'yyyy': {
707
+ regex: '\\d{4}',
708
+ apply: function(value) { this.year = +value; }
709
+ },
710
+ 'yy': {
711
+ regex: '\\d{2}',
712
+ apply: function(value) { this.year = +value + 2000; }
713
+ },
714
+ 'y': {
715
+ regex: '\\d{1,4}',
716
+ apply: function(value) { this.year = +value; }
717
+ },
718
+ 'MMMM': {
719
+ regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
720
+ apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); }
721
+ },
722
+ 'MMM': {
723
+ regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
724
+ apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); }
725
+ },
726
+ 'MM': {
727
+ regex: '0[1-9]|1[0-2]',
728
+ apply: function(value) { this.month = value - 1; }
729
+ },
730
+ 'M': {
731
+ regex: '[1-9]|1[0-2]',
732
+ apply: function(value) { this.month = value - 1; }
733
+ },
734
+ 'dd': {
735
+ regex: '[0-2][0-9]{1}|3[0-1]{1}',
736
+ apply: function(value) { this.date = +value; }
737
+ },
738
+ 'd': {
739
+ regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
740
+ apply: function(value) { this.date = +value; }
741
+ },
742
+ 'EEEE': {
743
+ regex: $locale.DATETIME_FORMATS.DAY.join('|')
744
+ },
745
+ 'EEE': {
746
+ regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|')
747
+ }
748
+ };
749
+
750
+ this.createParser = function(format) {
751
+ var map = [], regex = format.split('');
752
+
753
+ angular.forEach(formatCodeToRegex, function(data, code) {
754
+ var index = format.indexOf(code);
755
+
756
+ if (index > -1) {
757
+ format = format.split('');
758
+
759
+ regex[index] = '(' + data.regex + ')';
760
+ format[index] = '$'; // Custom symbol to define consumed part of format
761
+ for (var i = index + 1, n = index + code.length; i < n; i++) {
762
+ regex[i] = '';
763
+ format[i] = '$';
764
+ }
765
+ format = format.join('');
766
+
767
+ map.push({ index: index, apply: data.apply });
768
+ }
769
+ });
770
+
771
+ return {
772
+ regex: new RegExp('^' + regex.join('') + '$'),
773
+ map: orderByFilter(map, 'index')
774
+ };
775
+ };
776
+
777
+ this.parse = function(input, format) {
778
+ if ( !angular.isString(input) ) {
779
+ return input;
780
+ }
781
+
782
+ format = $locale.DATETIME_FORMATS[format] || format;
783
+
784
+ if ( !this.parsers[format] ) {
785
+ this.parsers[format] = this.createParser(format);
786
+ }
787
+
788
+ var parser = this.parsers[format],
789
+ regex = parser.regex,
790
+ map = parser.map,
791
+ results = input.match(regex);
792
+
793
+ if ( results && results.length ) {
794
+ var fields = { year: 1900, month: 0, date: 1, hours: 0 }, dt;
795
+
796
+ for( var i = 1, n = results.length; i < n; i++ ) {
797
+ var mapper = map[i-1];
798
+ if ( mapper.apply ) {
799
+ mapper.apply.call(fields, results[i]);
800
+ }
801
+ }
802
+
803
+ if ( isValid(fields.year, fields.month, fields.date) ) {
804
+ dt = new Date( fields.year, fields.month, fields.date, fields.hours);
805
+ }
806
+
807
+ return dt;
808
+ }
809
+ };
810
+
811
+ // Check if date is valid for specific month (and year for February).
812
+ // Month: 0 = Jan, 1 = Feb, etc
813
+ function isValid(year, month, date) {
814
+ if ( month === 1 && date > 28) {
815
+ return date === 29 && ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
816
+ }
817
+
818
+ if ( month === 3 || month === 5 || month === 8 || month === 10) {
819
+ return date < 31;
820
+ }
821
+
822
+ return true;
823
+ }
755
824
  }]);
756
825
 
757
826
  angular.module('ui.bootstrap.position', [])
@@ -779,7 +848,7 @@ angular.module('ui.bootstrap.position', [])
779
848
  * @param element - raw DOM element
780
849
  */
781
850
  function isStaticPositioned(element) {
782
- return (getStyle(element, "position") || 'static' ) === 'static';
851
+ return (getStyle(element, 'position') || 'static' ) === 'static';
783
852
  }
784
853
 
785
854
  /**
@@ -828,22 +897,97 @@ angular.module('ui.bootstrap.position', [])
828
897
  return {
829
898
  width: boundingClientRect.width || element.prop('offsetWidth'),
830
899
  height: boundingClientRect.height || element.prop('offsetHeight'),
831
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop || $document[0].documentElement.scrollTop),
832
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft || $document[0].documentElement.scrollLeft)
900
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
901
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
833
902
  };
903
+ },
904
+
905
+ /**
906
+ * Provides coordinates for the targetEl in relation to hostEl
907
+ */
908
+ positionElements: function (hostEl, targetEl, positionStr, appendToBody) {
909
+
910
+ var positionStrParts = positionStr.split('-');
911
+ var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
912
+
913
+ var hostElPos,
914
+ targetElWidth,
915
+ targetElHeight,
916
+ targetElPos;
917
+
918
+ hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
919
+
920
+ targetElWidth = targetEl.prop('offsetWidth');
921
+ targetElHeight = targetEl.prop('offsetHeight');
922
+
923
+ var shiftWidth = {
924
+ center: function () {
925
+ return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
926
+ },
927
+ left: function () {
928
+ return hostElPos.left;
929
+ },
930
+ right: function () {
931
+ return hostElPos.left + hostElPos.width;
932
+ }
933
+ };
934
+
935
+ var shiftHeight = {
936
+ center: function () {
937
+ return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
938
+ },
939
+ top: function () {
940
+ return hostElPos.top;
941
+ },
942
+ bottom: function () {
943
+ return hostElPos.top + hostElPos.height;
944
+ }
945
+ };
946
+
947
+ switch (pos0) {
948
+ case 'right':
949
+ targetElPos = {
950
+ top: shiftHeight[pos1](),
951
+ left: shiftWidth[pos0]()
952
+ };
953
+ break;
954
+ case 'left':
955
+ targetElPos = {
956
+ top: shiftHeight[pos1](),
957
+ left: hostElPos.left - targetElWidth
958
+ };
959
+ break;
960
+ case 'bottom':
961
+ targetElPos = {
962
+ top: shiftHeight[pos0](),
963
+ left: shiftWidth[pos1]()
964
+ };
965
+ break;
966
+ default:
967
+ targetElPos = {
968
+ top: hostElPos.top - targetElHeight,
969
+ left: shiftWidth[pos1]()
970
+ };
971
+ break;
972
+ }
973
+
974
+ return targetElPos;
834
975
  }
835
976
  };
836
977
  }]);
837
978
 
838
- angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
979
+ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.position'])
839
980
 
840
981
  .constant('datepickerConfig', {
841
- dayFormat: 'dd',
842
- monthFormat: 'MMMM',
843
- yearFormat: 'yyyy',
844
- dayHeaderFormat: 'EEE',
845
- dayTitleFormat: 'MMMM yyyy',
846
- monthTitleFormat: 'yyyy',
982
+ formatDay: 'dd',
983
+ formatMonth: 'MMMM',
984
+ formatYear: 'yyyy',
985
+ formatDayHeader: 'EEE',
986
+ formatDayTitle: 'MMMM yyyy',
987
+ formatMonthTitle: 'yyyy',
988
+ datepickerMode: 'day',
989
+ minMode: 'day',
990
+ maxMode: 'year',
847
991
  showWeeks: true,
848
992
  startingDay: 0,
849
993
  yearRange: 20,
@@ -851,229 +995,256 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
851
995
  maxDate: null
852
996
  })
853
997
 
854
- .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
855
- var format = {
856
- day: getValue($attrs.dayFormat, dtConfig.dayFormat),
857
- month: getValue($attrs.monthFormat, dtConfig.monthFormat),
858
- year: getValue($attrs.yearFormat, dtConfig.yearFormat),
859
- dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
860
- dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
861
- monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
862
- },
863
- startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
864
- yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
865
-
866
- this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
867
- this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
868
-
869
- function getValue(value, defaultValue) {
870
- return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
871
- }
998
+ .controller('DatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$timeout', '$log', 'dateFilter', 'datepickerConfig', function($scope, $attrs, $parse, $interpolate, $timeout, $log, dateFilter, datepickerConfig) {
999
+ var self = this,
1000
+ ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl;
872
1001
 
873
- function getDaysInMonth( year, month ) {
874
- return new Date(year, month, 0).getDate();
875
- }
1002
+ // Modes chain
1003
+ this.modes = ['day', 'month', 'year'];
876
1004
 
877
- function getDates(startDate, n) {
878
- var dates = new Array(n);
879
- var current = startDate, i = 0;
880
- while (i < n) {
881
- dates[i++] = new Date(current);
882
- current.setDate( current.getDate() + 1 );
1005
+ // Configuration attributes
1006
+ angular.forEach(['formatDay', 'formatMonth', 'formatYear', 'formatDayHeader', 'formatDayTitle', 'formatMonthTitle',
1007
+ 'minMode', 'maxMode', 'showWeeks', 'startingDay', 'yearRange'], function( key, index ) {
1008
+ self[key] = angular.isDefined($attrs[key]) ? (index < 8 ? $interpolate($attrs[key])($scope.$parent) : $scope.$parent.$eval($attrs[key])) : datepickerConfig[key];
1009
+ });
1010
+
1011
+ // Watchable attributes
1012
+ angular.forEach(['minDate', 'maxDate'], function( key ) {
1013
+ if ( $attrs[key] ) {
1014
+ $scope.$parent.$watch($parse($attrs[key]), function(value) {
1015
+ self[key] = value ? new Date(value) : null;
1016
+ self.refreshView();
1017
+ });
1018
+ } else {
1019
+ self[key] = datepickerConfig[key] ? new Date(datepickerConfig[key]) : null;
883
1020
  }
884
- return dates;
885
- }
1021
+ });
886
1022
 
887
- function makeDate(date, format, isSelected, isSecondary) {
888
- return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
889
- }
1023
+ $scope.datepickerMode = $scope.datepickerMode || datepickerConfig.datepickerMode;
1024
+ $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1025
+ this.activeDate = angular.isDefined($attrs.initDate) ? $scope.$parent.$eval($attrs.initDate) : new Date();
890
1026
 
891
- this.modes = [
892
- {
893
- name: 'day',
894
- getVisibleDates: function(date, selected) {
895
- var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
896
- var difference = startingDay - firstDayOfMonth.getDay(),
897
- numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
898
- firstDate = new Date(firstDayOfMonth), numDates = 0;
1027
+ $scope.isActive = function(dateObject) {
1028
+ if (self.compare(dateObject.date, self.activeDate) === 0) {
1029
+ $scope.activeDateId = dateObject.uid;
1030
+ return true;
1031
+ }
1032
+ return false;
1033
+ };
899
1034
 
900
- if ( numDisplayedFromPreviousMonth > 0 ) {
901
- firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
902
- numDates += numDisplayedFromPreviousMonth; // Previous
903
- }
904
- numDates += getDaysInMonth(year, month + 1); // Current
905
- numDates += (7 - numDates % 7) % 7; // Next
1035
+ this.init = function( ngModelCtrl_ ) {
1036
+ ngModelCtrl = ngModelCtrl_;
906
1037
 
907
- var days = getDates(firstDate, numDates), labels = new Array(7);
908
- for (var i = 0; i < numDates; i ++) {
909
- var dt = new Date(days[i]);
910
- days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
911
- }
912
- for (var j = 0; j < 7; j++) {
913
- labels[j] = dateFilter(days[j].date, format.dayHeader);
914
- }
915
- return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
916
- },
917
- compare: function(date1, date2) {
918
- return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
919
- },
920
- split: 7,
921
- step: { months: 1 }
922
- },
923
- {
924
- name: 'month',
925
- getVisibleDates: function(date, selected) {
926
- var months = new Array(12), year = date.getFullYear();
927
- for ( var i = 0; i < 12; i++ ) {
928
- var dt = new Date(year, i, 1);
929
- months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
930
- }
931
- return { objects: months, title: dateFilter(date, format.monthTitle) };
932
- },
933
- compare: function(date1, date2) {
934
- return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
935
- },
936
- split: 3,
937
- step: { years: 1 }
938
- },
939
- {
940
- name: 'year',
941
- getVisibleDates: function(date, selected) {
942
- var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
943
- for ( var i = 0; i < yearRange; i++ ) {
944
- var dt = new Date(startYear + i, 0, 1);
945
- years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
946
- }
947
- return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
948
- },
949
- compare: function(date1, date2) {
950
- return date1.getFullYear() - date2.getFullYear();
951
- },
952
- split: 5,
953
- step: { years: yearRange }
1038
+ ngModelCtrl.$render = function() {
1039
+ self.render();
1040
+ };
1041
+ };
1042
+
1043
+ this.render = function() {
1044
+ if ( ngModelCtrl.$modelValue ) {
1045
+ var date = new Date( ngModelCtrl.$modelValue ),
1046
+ isValid = !isNaN(date);
1047
+
1048
+ if ( isValid ) {
1049
+ this.activeDate = date;
1050
+ } else {
1051
+ $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.');
1052
+ }
1053
+ ngModelCtrl.$setValidity('date', isValid);
954
1054
  }
955
- ];
1055
+ this.refreshView();
1056
+ };
956
1057
 
957
- this.isDisabled = function(date, mode) {
958
- var currentMode = this.modes[mode || 0];
959
- return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
1058
+ this.refreshView = function() {
1059
+ if ( this.element ) {
1060
+ this._refreshView();
1061
+
1062
+ var date = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
1063
+ ngModelCtrl.$setValidity('date-disabled', !date || (this.element && !this.isDisabled(date)));
1064
+ }
1065
+ };
1066
+
1067
+ this.createDateObject = function(date, format) {
1068
+ var model = ngModelCtrl.$modelValue ? new Date(ngModelCtrl.$modelValue) : null;
1069
+ return {
1070
+ date: date,
1071
+ label: dateFilter(date, format),
1072
+ selected: model && this.compare(date, model) === 0,
1073
+ disabled: this.isDisabled(date),
1074
+ current: this.compare(date, new Date()) === 0
1075
+ };
1076
+ };
1077
+
1078
+ this.isDisabled = function( date ) {
1079
+ 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})));
1080
+ };
1081
+
1082
+ // Split array into smaller arrays
1083
+ this.split = function(arr, size) {
1084
+ var arrays = [];
1085
+ while (arr.length > 0) {
1086
+ arrays.push(arr.splice(0, size));
1087
+ }
1088
+ return arrays;
1089
+ };
1090
+
1091
+ $scope.select = function( date ) {
1092
+ if ( $scope.datepickerMode === self.minMode ) {
1093
+ var dt = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1094
+ dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1095
+ ngModelCtrl.$setViewValue( dt );
1096
+ ngModelCtrl.$render();
1097
+ } else {
1098
+ self.activeDate = date;
1099
+ $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) - 1 ];
1100
+ }
1101
+ };
1102
+
1103
+ $scope.move = function( direction ) {
1104
+ var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1105
+ month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1106
+ self.activeDate.setFullYear(year, month, 1);
1107
+ self.refreshView();
1108
+ };
1109
+
1110
+ $scope.toggleMode = function( direction ) {
1111
+ direction = direction || 1;
1112
+
1113
+ if (($scope.datepickerMode === self.maxMode && direction === 1) || ($scope.datepickerMode === self.minMode && direction === -1)) {
1114
+ return;
1115
+ }
1116
+
1117
+ $scope.datepickerMode = self.modes[ self.modes.indexOf( $scope.datepickerMode ) + direction ];
1118
+ };
1119
+
1120
+ // Key event mapper
1121
+ $scope.keys = { 13:'enter', 32:'space', 33:'pageup', 34:'pagedown', 35:'end', 36:'home', 37:'left', 38:'up', 39:'right', 40:'down' };
1122
+
1123
+ var focusElement = function() {
1124
+ $timeout(function() {
1125
+ self.element[0].focus();
1126
+ }, 0 , false);
1127
+ };
1128
+
1129
+ // Listen for focus requests from popup directive
1130
+ $scope.$on('datepicker.focus', focusElement);
1131
+
1132
+ $scope.keydown = function( evt ) {
1133
+ var key = $scope.keys[evt.which];
1134
+
1135
+ if ( !key || evt.shiftKey || evt.altKey ) {
1136
+ return;
1137
+ }
1138
+
1139
+ evt.preventDefault();
1140
+ evt.stopPropagation();
1141
+
1142
+ if (key === 'enter' || key === 'space') {
1143
+ if ( self.isDisabled(self.activeDate)) {
1144
+ return; // do nothing
1145
+ }
1146
+ $scope.select(self.activeDate);
1147
+ focusElement();
1148
+ } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
1149
+ $scope.toggleMode(key === 'up' ? 1 : -1);
1150
+ focusElement();
1151
+ } else {
1152
+ self.handleKeyDown(key, evt);
1153
+ self.refreshView();
1154
+ }
960
1155
  };
961
1156
  }])
962
1157
 
963
- .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
1158
+ .directive( 'datepicker', function () {
964
1159
  return {
965
1160
  restrict: 'EA',
966
1161
  replace: true,
967
1162
  templateUrl: 'template/datepicker/datepicker.html',
968
1163
  scope: {
1164
+ datepickerMode: '=?',
969
1165
  dateDisabled: '&'
970
1166
  },
971
1167
  require: ['datepicker', '?^ngModel'],
972
1168
  controller: 'DatepickerController',
973
1169
  link: function(scope, element, attrs, ctrls) {
974
- var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
1170
+ var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
975
1171
 
976
- if (!ngModel) {
977
- return; // do nothing if no ng-model
1172
+ if ( ngModelCtrl ) {
1173
+ datepickerCtrl.init( ngModelCtrl );
978
1174
  }
1175
+ }
1176
+ };
1177
+ })
979
1178
 
980
- // Configuration parameters
981
- var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
982
-
983
- if (attrs.showWeeks) {
984
- scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
985
- showWeeks = !! value;
986
- updateShowWeekNumbers();
987
- });
988
- } else {
989
- updateShowWeekNumbers();
990
- }
1179
+ .directive('daypicker', ['dateFilter', function (dateFilter) {
1180
+ return {
1181
+ restrict: 'EA',
1182
+ replace: true,
1183
+ templateUrl: 'template/datepicker/day.html',
1184
+ require: '^datepicker',
1185
+ link: function(scope, element, attrs, ctrl) {
1186
+ scope.showWeeks = ctrl.showWeeks;
991
1187
 
992
- if (attrs.min) {
993
- scope.$parent.$watch($parse(attrs.min), function(value) {
994
- datepickerCtrl.minDate = value ? new Date(value) : null;
995
- refill();
996
- });
997
- }
998
- if (attrs.max) {
999
- scope.$parent.$watch($parse(attrs.max), function(value) {
1000
- datepickerCtrl.maxDate = value ? new Date(value) : null;
1001
- refill();
1002
- });
1003
- }
1188
+ ctrl.step = { months: 1 };
1189
+ ctrl.element = element;
1004
1190
 
1005
- function updateShowWeekNumbers() {
1006
- scope.showWeekNumbers = mode === 0 && showWeeks;
1191
+ var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1192
+ function getDaysInMonth( year, month ) {
1193
+ return ((month === 1) && (year % 4 === 0) && ((year % 100 !== 0) || (year % 400 === 0))) ? 29 : DAYS_IN_MONTH[month];
1007
1194
  }
1008
1195
 
1009
- // Split array into smaller arrays
1010
- function split(arr, size) {
1011
- var arrays = [];
1012
- while (arr.length > 0) {
1013
- arrays.push(arr.splice(0, size));
1196
+ function getDates(startDate, n) {
1197
+ var dates = new Array(n), current = new Date(startDate), i = 0;
1198
+ current.setHours(12); // Prevent repeated dates because of timezone bug
1199
+ while ( i < n ) {
1200
+ dates[i++] = new Date(current);
1201
+ current.setDate( current.getDate() + 1 );
1014
1202
  }
1015
- return arrays;
1203
+ return dates;
1016
1204
  }
1017
1205
 
1018
- function refill( updateSelected ) {
1019
- var date = null, valid = true;
1206
+ ctrl._refreshView = function() {
1207
+ var year = ctrl.activeDate.getFullYear(),
1208
+ month = ctrl.activeDate.getMonth(),
1209
+ firstDayOfMonth = new Date(year, month, 1),
1210
+ difference = ctrl.startingDay - firstDayOfMonth.getDay(),
1211
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
1212
+ firstDate = new Date(firstDayOfMonth);
1020
1213
 
1021
- if ( ngModel.$modelValue ) {
1022
- date = new Date( ngModel.$modelValue );
1023
-
1024
- if ( isNaN(date) ) {
1025
- valid = false;
1026
- $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.');
1027
- } else if ( updateSelected ) {
1028
- selected = date;
1029
- }
1214
+ if ( numDisplayedFromPreviousMonth > 0 ) {
1215
+ firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
1030
1216
  }
1031
- ngModel.$setValidity('date', valid);
1032
-
1033
- var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
1034
- angular.forEach(data.objects, function(obj) {
1035
- obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
1036
- });
1037
-
1038
- ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
1039
1217
 
1040
- scope.rows = split(data.objects, currentMode.split);
1041
- scope.labels = data.labels || [];
1042
- scope.title = data.title;
1043
- }
1218
+ // 42 is the number of days on a six-month calendar
1219
+ var days = getDates(firstDate, 42);
1220
+ for (var i = 0; i < 42; i ++) {
1221
+ days[i] = angular.extend(ctrl.createDateObject(days[i], ctrl.formatDay), {
1222
+ secondary: days[i].getMonth() !== month,
1223
+ uid: scope.uniqueId + '-' + i
1224
+ });
1225
+ }
1044
1226
 
1045
- function setMode(value) {
1046
- mode = value;
1047
- updateShowWeekNumbers();
1048
- refill();
1049
- }
1227
+ scope.labels = new Array(7);
1228
+ for (var j = 0; j < 7; j++) {
1229
+ scope.labels[j] = {
1230
+ abbr: dateFilter(days[j].date, ctrl.formatDayHeader),
1231
+ full: dateFilter(days[j].date, 'EEEE')
1232
+ };
1233
+ }
1050
1234
 
1051
- ngModel.$render = function() {
1052
- refill( true );
1053
- };
1235
+ scope.title = dateFilter(ctrl.activeDate, ctrl.formatDayTitle);
1236
+ scope.rows = ctrl.split(days, 7);
1054
1237
 
1055
- scope.select = function( date ) {
1056
- if ( mode === 0 ) {
1057
- var dt = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : new Date(0, 0, 0, 0, 0, 0, 0);
1058
- dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1059
- ngModel.$setViewValue( dt );
1060
- refill( true );
1061
- } else {
1062
- selected = date;
1063
- setMode( mode - 1 );
1238
+ if ( scope.showWeeks ) {
1239
+ scope.weekNumbers = [];
1240
+ var weekNumber = getISO8601WeekNumber( scope.rows[0][0].date ),
1241
+ numWeeks = scope.rows.length;
1242
+ while( scope.weekNumbers.push(weekNumber++) < numWeeks ) {}
1064
1243
  }
1065
1244
  };
1066
- scope.move = function(direction) {
1067
- var step = datepickerCtrl.modes[mode].step;
1068
- selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
1069
- selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
1070
- refill();
1071
- };
1072
- scope.toggleMode = function() {
1073
- setMode( (mode + 1) % datepickerCtrl.modes.length );
1074
- };
1075
- scope.getWeekNumber = function(row) {
1076
- return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
1245
+
1246
+ ctrl.compare = function(date1, date2) {
1247
+ return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
1077
1248
  };
1078
1249
 
1079
1250
  function getISO8601WeekNumber(date) {
@@ -1084,14 +1255,152 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
1084
1255
  checkDate.setDate(1);
1085
1256
  return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1086
1257
  }
1258
+
1259
+ ctrl.handleKeyDown = function( key, evt ) {
1260
+ var date = ctrl.activeDate.getDate();
1261
+
1262
+ if (key === 'left') {
1263
+ date = date - 1; // up
1264
+ } else if (key === 'up') {
1265
+ date = date - 7; // down
1266
+ } else if (key === 'right') {
1267
+ date = date + 1; // down
1268
+ } else if (key === 'down') {
1269
+ date = date + 7;
1270
+ } else if (key === 'pageup' || key === 'pagedown') {
1271
+ var month = ctrl.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
1272
+ ctrl.activeDate.setMonth(month, 1);
1273
+ date = Math.min(getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth()), date);
1274
+ } else if (key === 'home') {
1275
+ date = 1;
1276
+ } else if (key === 'end') {
1277
+ date = getDaysInMonth(ctrl.activeDate.getFullYear(), ctrl.activeDate.getMonth());
1278
+ }
1279
+ ctrl.activeDate.setDate(date);
1280
+ };
1281
+
1282
+ ctrl.refreshView();
1283
+ }
1284
+ };
1285
+ }])
1286
+
1287
+ .directive('monthpicker', ['dateFilter', function (dateFilter) {
1288
+ return {
1289
+ restrict: 'EA',
1290
+ replace: true,
1291
+ templateUrl: 'template/datepicker/month.html',
1292
+ require: '^datepicker',
1293
+ link: function(scope, element, attrs, ctrl) {
1294
+ ctrl.step = { years: 1 };
1295
+ ctrl.element = element;
1296
+
1297
+ ctrl._refreshView = function() {
1298
+ var months = new Array(12),
1299
+ year = ctrl.activeDate.getFullYear();
1300
+
1301
+ for ( var i = 0; i < 12; i++ ) {
1302
+ months[i] = angular.extend(ctrl.createDateObject(new Date(year, i, 1), ctrl.formatMonth), {
1303
+ uid: scope.uniqueId + '-' + i
1304
+ });
1305
+ }
1306
+
1307
+ scope.title = dateFilter(ctrl.activeDate, ctrl.formatMonthTitle);
1308
+ scope.rows = ctrl.split(months, 3);
1309
+ };
1310
+
1311
+ ctrl.compare = function(date1, date2) {
1312
+ return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
1313
+ };
1314
+
1315
+ ctrl.handleKeyDown = function( key, evt ) {
1316
+ var date = ctrl.activeDate.getMonth();
1317
+
1318
+ if (key === 'left') {
1319
+ date = date - 1; // up
1320
+ } else if (key === 'up') {
1321
+ date = date - 3; // down
1322
+ } else if (key === 'right') {
1323
+ date = date + 1; // down
1324
+ } else if (key === 'down') {
1325
+ date = date + 3;
1326
+ } else if (key === 'pageup' || key === 'pagedown') {
1327
+ var year = ctrl.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
1328
+ ctrl.activeDate.setFullYear(year);
1329
+ } else if (key === 'home') {
1330
+ date = 0;
1331
+ } else if (key === 'end') {
1332
+ date = 11;
1333
+ }
1334
+ ctrl.activeDate.setMonth(date);
1335
+ };
1336
+
1337
+ ctrl.refreshView();
1338
+ }
1339
+ };
1340
+ }])
1341
+
1342
+ .directive('yearpicker', ['dateFilter', function (dateFilter) {
1343
+ return {
1344
+ restrict: 'EA',
1345
+ replace: true,
1346
+ templateUrl: 'template/datepicker/year.html',
1347
+ require: '^datepicker',
1348
+ link: function(scope, element, attrs, ctrl) {
1349
+ var range = ctrl.yearRange;
1350
+
1351
+ ctrl.step = { years: range };
1352
+ ctrl.element = element;
1353
+
1354
+ function getStartingYear( year ) {
1355
+ return parseInt((year - 1) / range, 10) * range + 1;
1356
+ }
1357
+
1358
+ ctrl._refreshView = function() {
1359
+ var years = new Array(range);
1360
+
1361
+ for ( var i = 0, start = getStartingYear(ctrl.activeDate.getFullYear()); i < range; i++ ) {
1362
+ years[i] = angular.extend(ctrl.createDateObject(new Date(start + i, 0, 1), ctrl.formatYear), {
1363
+ uid: scope.uniqueId + '-' + i
1364
+ });
1365
+ }
1366
+
1367
+ scope.title = [years[0].label, years[range - 1].label].join(' - ');
1368
+ scope.rows = ctrl.split(years, 5);
1369
+ };
1370
+
1371
+ ctrl.compare = function(date1, date2) {
1372
+ return date1.getFullYear() - date2.getFullYear();
1373
+ };
1374
+
1375
+ ctrl.handleKeyDown = function( key, evt ) {
1376
+ var date = ctrl.activeDate.getFullYear();
1377
+
1378
+ if (key === 'left') {
1379
+ date = date - 1; // up
1380
+ } else if (key === 'up') {
1381
+ date = date - 5; // down
1382
+ } else if (key === 'right') {
1383
+ date = date + 1; // down
1384
+ } else if (key === 'down') {
1385
+ date = date + 5;
1386
+ } else if (key === 'pageup' || key === 'pagedown') {
1387
+ date += (key === 'pageup' ? - 1 : 1) * ctrl.step.years;
1388
+ } else if (key === 'home') {
1389
+ date = getStartingYear( ctrl.activeDate.getFullYear() );
1390
+ } else if (key === 'end') {
1391
+ date = getStartingYear( ctrl.activeDate.getFullYear() ) + range - 1;
1392
+ }
1393
+ ctrl.activeDate.setFullYear(date);
1394
+ };
1395
+
1396
+ ctrl.refreshView();
1087
1397
  }
1088
1398
  };
1089
1399
  }])
1090
1400
 
1091
1401
  .constant('datepickerPopupConfig', {
1092
- dateFormat: 'yyyy-MM-dd',
1402
+ datepickerPopup: 'yyyy-MM-dd',
1093
1403
  currentText: 'Today',
1094
- toggleWeeksText: 'Weeks',
1095
1404
  clearText: 'Clear',
1096
1405
  closeText: 'Done',
1097
1406
  closeOnDateSelection: true,
@@ -1099,98 +1408,74 @@ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
1099
1408
  showButtonBar: true
1100
1409
  })
1101
1410
 
1102
- .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig', 'datepickerConfig',
1103
- function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig, datepickerConfig) {
1411
+ .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'dateParser', 'datepickerPopupConfig',
1412
+ function ($compile, $parse, $document, $position, dateFilter, dateParser, datepickerPopupConfig) {
1104
1413
  return {
1105
1414
  restrict: 'EA',
1106
1415
  require: 'ngModel',
1107
- link: function(originalScope, element, attrs, ngModel) {
1108
- var scope = originalScope.$new(), // create a child scope so we are not polluting original one
1109
- dateFormat,
1110
- closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? originalScope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
1111
- appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? originalScope.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
1416
+ scope: {
1417
+ isOpen: '=?',
1418
+ currentText: '@',
1419
+ clearText: '@',
1420
+ closeText: '@',
1421
+ dateDisabled: '&'
1422
+ },
1423
+ link: function(scope, element, attrs, ngModel) {
1424
+ var dateFormat,
1425
+ closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$parent.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection,
1426
+ appendToBody = angular.isDefined(attrs.datepickerAppendToBody) ? scope.$parent.$eval(attrs.datepickerAppendToBody) : datepickerPopupConfig.appendToBody;
1112
1427
 
1113
- attrs.$observe('datepickerPopup', function(value) {
1114
- dateFormat = value || datepickerPopupConfig.dateFormat;
1115
- ngModel.$render();
1116
- });
1428
+ scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? scope.$parent.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
1117
1429
 
1118
- scope.showButtonBar = angular.isDefined(attrs.showButtonBar) ? originalScope.$eval(attrs.showButtonBar) : datepickerPopupConfig.showButtonBar;
1430
+ scope.getText = function( key ) {
1431
+ return scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
1432
+ };
1119
1433
 
1120
- originalScope.$on('$destroy', function() {
1121
- $popup.remove();
1122
- scope.$destroy();
1434
+ attrs.$observe('datepickerPopup', function(value) {
1435
+ dateFormat = value || datepickerPopupConfig.datepickerPopup;
1436
+ ngModel.$render();
1123
1437
  });
1124
1438
 
1125
- attrs.$observe('currentText', function(text) {
1126
- scope.currentText = angular.isDefined(text) ? text : datepickerPopupConfig.currentText;
1127
- });
1128
- attrs.$observe('toggleWeeksText', function(text) {
1129
- scope.toggleWeeksText = angular.isDefined(text) ? text : datepickerPopupConfig.toggleWeeksText;
1130
- });
1131
- attrs.$observe('clearText', function(text) {
1132
- scope.clearText = angular.isDefined(text) ? text : datepickerPopupConfig.clearText;
1133
- });
1134
- attrs.$observe('closeText', function(text) {
1135
- scope.closeText = angular.isDefined(text) ? text : datepickerPopupConfig.closeText;
1439
+ // popup element used to display calendar
1440
+ var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
1441
+ popupEl.attr({
1442
+ 'ng-model': 'date',
1443
+ 'ng-change': 'dateSelection()'
1136
1444
  });
1137
1445
 
1138
- var getIsOpen, setIsOpen;
1139
- if ( attrs.isOpen ) {
1140
- getIsOpen = $parse(attrs.isOpen);
1141
- setIsOpen = getIsOpen.assign;
1142
-
1143
- originalScope.$watch(getIsOpen, function updateOpen(value) {
1144
- scope.isOpen = !! value;
1145
- });
1446
+ function cameltoDash( string ){
1447
+ return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
1146
1448
  }
1147
- scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
1148
1449
 
1149
- function setOpen( value ) {
1150
- if (setIsOpen) {
1151
- setIsOpen(originalScope, !!value);
1152
- } else {
1153
- scope.isOpen = !!value;
1154
- }
1450
+ // datepicker element
1451
+ var datepickerEl = angular.element(popupEl.children()[0]);
1452
+ if ( attrs.datepickerOptions ) {
1453
+ angular.forEach(scope.$parent.$eval(attrs.datepickerOptions), function( value, option ) {
1454
+ datepickerEl.attr( cameltoDash(option), value );
1455
+ });
1155
1456
  }
1156
1457
 
1157
- var documentClickBind = function(event) {
1158
- if (scope.isOpen && event.target !== element[0]) {
1159
- scope.$apply(function() {
1160
- setOpen(false);
1458
+ angular.forEach(['minDate', 'maxDate'], function( key ) {
1459
+ if ( attrs[key] ) {
1460
+ scope.$parent.$watch($parse(attrs[key]), function(value){
1461
+ scope[key] = value;
1161
1462
  });
1463
+ datepickerEl.attr(cameltoDash(key), key);
1162
1464
  }
1163
- };
1164
-
1165
- var elementFocusBind = function() {
1166
- scope.$apply(function() {
1167
- setOpen( true );
1168
- });
1169
- };
1170
-
1171
- // popup element used to display calendar
1172
- var popupEl = angular.element('<div datepicker-popup-wrap><div datepicker></div></div>');
1173
- popupEl.attr({
1174
- 'ng-model': 'date',
1175
- 'ng-change': 'dateSelection()'
1176
1465
  });
1177
- var datepickerEl = angular.element(popupEl.children()[0]),
1178
- datepickerOptions = {};
1179
- if (attrs.datepickerOptions) {
1180
- datepickerOptions = originalScope.$eval(attrs.datepickerOptions);
1181
- datepickerEl.attr(angular.extend({}, datepickerOptions));
1466
+ if (attrs.dateDisabled) {
1467
+ datepickerEl.attr('date-disabled', 'dateDisabled({ date: date, mode: mode })');
1182
1468
  }
1183
1469
 
1184
- // TODO: reverse from dateFilter string to Date object
1185
1470
  function parseDate(viewValue) {
1186
1471
  if (!viewValue) {
1187
1472
  ngModel.$setValidity('date', true);
1188
1473
  return null;
1189
- } else if (angular.isDate(viewValue)) {
1474
+ } else if (angular.isDate(viewValue) && !isNaN(viewValue)) {
1190
1475
  ngModel.$setValidity('date', true);
1191
1476
  return viewValue;
1192
1477
  } else if (angular.isString(viewValue)) {
1193
- var date = new Date(viewValue);
1478
+ var date = dateParser.parse(viewValue, dateFormat) || new Date(viewValue);
1194
1479
  if (isNaN(date)) {
1195
1480
  ngModel.$setValidity('date', false);
1196
1481
  return undefined;
@@ -1213,8 +1498,9 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1213
1498
  ngModel.$setViewValue(scope.date);
1214
1499
  ngModel.$render();
1215
1500
 
1216
- if (closeOnDateSelection) {
1217
- setOpen( false );
1501
+ if ( closeOnDateSelection ) {
1502
+ scope.isOpen = false;
1503
+ element[0].focus();
1218
1504
  }
1219
1505
  };
1220
1506
 
@@ -1228,62 +1514,60 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1228
1514
  ngModel.$render = function() {
1229
1515
  var date = ngModel.$viewValue ? dateFilter(ngModel.$viewValue, dateFormat) : '';
1230
1516
  element.val(date);
1231
- scope.date = ngModel.$modelValue;
1517
+ scope.date = parseDate( ngModel.$modelValue );
1232
1518
  };
1233
1519
 
1234
- function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1235
- if (attribute) {
1236
- originalScope.$watch($parse(attribute), function(value){
1237
- scope[scopeProperty] = value;
1520
+ var documentClickBind = function(event) {
1521
+ if (scope.isOpen && event.target !== element[0]) {
1522
+ scope.$apply(function() {
1523
+ scope.isOpen = false;
1238
1524
  });
1239
- datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
1240
1525
  }
1241
- }
1242
- addWatchableAttribute(attrs.min, 'min');
1243
- addWatchableAttribute(attrs.max, 'max');
1244
- if (attrs.showWeeks) {
1245
- addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
1246
- } else {
1247
- scope.showWeeks = 'show-weeks' in datepickerOptions ? datepickerOptions['show-weeks'] : datepickerConfig.showWeeks;
1248
- datepickerEl.attr('show-weeks', 'showWeeks');
1249
- }
1250
- if (attrs.dateDisabled) {
1251
- datepickerEl.attr('date-disabled', attrs.dateDisabled);
1252
- }
1526
+ };
1253
1527
 
1254
- function updatePosition() {
1255
- scope.position = appendToBody ? $position.offset(element) : $position.position(element);
1256
- scope.position.top = scope.position.top + element.prop('offsetHeight');
1257
- }
1528
+ var keydown = function(evt, noApply) {
1529
+ scope.keydown(evt);
1530
+ };
1531
+ element.bind('keydown', keydown);
1532
+
1533
+ scope.keydown = function(evt) {
1534
+ if (evt.which === 27) {
1535
+ evt.preventDefault();
1536
+ evt.stopPropagation();
1537
+ scope.close();
1538
+ } else if (evt.which === 40 && !scope.isOpen) {
1539
+ scope.isOpen = true;
1540
+ }
1541
+ };
1258
1542
 
1259
- var documentBindingInitialized = false, elementFocusInitialized = false;
1260
1543
  scope.$watch('isOpen', function(value) {
1261
1544
  if (value) {
1262
- updatePosition();
1545
+ scope.$broadcast('datepicker.focus');
1546
+ scope.position = appendToBody ? $position.offset(element) : $position.position(element);
1547
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
1548
+
1263
1549
  $document.bind('click', documentClickBind);
1264
- if(elementFocusInitialized) {
1265
- element.unbind('focus', elementFocusBind);
1266
- }
1267
- element[0].focus();
1268
- documentBindingInitialized = true;
1269
1550
  } else {
1270
- if(documentBindingInitialized) {
1271
- $document.unbind('click', documentClickBind);
1272
- }
1273
- element.bind('focus', elementFocusBind);
1274
- elementFocusInitialized = true;
1275
- }
1276
-
1277
- if ( setIsOpen ) {
1278
- setIsOpen(originalScope, value);
1551
+ $document.unbind('click', documentClickBind);
1279
1552
  }
1280
1553
  });
1281
1554
 
1282
- scope.today = function() {
1283
- scope.dateSelection(new Date());
1555
+ scope.select = function( date ) {
1556
+ if (date === 'today') {
1557
+ var today = new Date();
1558
+ if (angular.isDate(ngModel.$modelValue)) {
1559
+ date = new Date(ngModel.$modelValue);
1560
+ date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
1561
+ } else {
1562
+ date = new Date(today.setHours(0, 0, 0, 0));
1563
+ }
1564
+ }
1565
+ scope.dateSelection( date );
1284
1566
  };
1285
- scope.clear = function() {
1286
- scope.dateSelection(null);
1567
+
1568
+ scope.close = function() {
1569
+ scope.isOpen = false;
1570
+ element[0].focus();
1287
1571
  };
1288
1572
 
1289
1573
  var $popup = $compile(popupEl)(scope);
@@ -1292,6 +1576,12 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1292
1576
  } else {
1293
1577
  element.after($popup);
1294
1578
  }
1579
+
1580
+ scope.$on('$destroy', function() {
1581
+ $popup.remove();
1582
+ element.unbind('keydown', keydown);
1583
+ $document.unbind('click', documentClickBind);
1584
+ });
1295
1585
  }
1296
1586
  };
1297
1587
  }])
@@ -1311,58 +1601,160 @@ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupCon
1311
1601
  };
1312
1602
  });
1313
1603
 
1314
- /*
1315
- * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
1316
- * @restrict class or attribute
1317
- * @example:
1318
- <li class="dropdown">
1319
- <a class="dropdown-toggle">My Dropdown Menu</a>
1320
- <ul class="dropdown-menu">
1321
- <li ng-repeat="choice in dropChoices">
1322
- <a ng-href="{{choice.href}}">{{choice.text}}</a>
1323
- </li>
1324
- </ul>
1325
- </li>
1326
- */
1604
+ angular.module('ui.bootstrap.dropdown', [])
1605
+
1606
+ .constant('dropdownConfig', {
1607
+ openClass: 'open'
1608
+ })
1609
+
1610
+ .service('dropdownService', ['$document', function($document) {
1611
+ var openScope = null;
1612
+
1613
+ this.open = function( dropdownScope ) {
1614
+ if ( !openScope ) {
1615
+ $document.bind('click', closeDropdown);
1616
+ $document.bind('keydown', escapeKeyBind);
1617
+ }
1327
1618
 
1328
- angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
1329
- var openElement = null,
1330
- closeMenu = angular.noop;
1619
+ if ( openScope && openScope !== dropdownScope ) {
1620
+ openScope.isOpen = false;
1621
+ }
1622
+
1623
+ openScope = dropdownScope;
1624
+ };
1625
+
1626
+ this.close = function( dropdownScope ) {
1627
+ if ( openScope === dropdownScope ) {
1628
+ openScope = null;
1629
+ $document.unbind('click', closeDropdown);
1630
+ $document.unbind('keydown', escapeKeyBind);
1631
+ }
1632
+ };
1633
+
1634
+ var closeDropdown = function( evt ) {
1635
+ if (evt && evt.isDefaultPrevented()) {
1636
+ return;
1637
+ }
1638
+
1639
+ openScope.$apply(function() {
1640
+ openScope.isOpen = false;
1641
+ });
1642
+ };
1643
+
1644
+ var escapeKeyBind = function( evt ) {
1645
+ if ( evt.which === 27 ) {
1646
+ openScope.focusToggleElement();
1647
+ closeDropdown();
1648
+ }
1649
+ };
1650
+ }])
1651
+
1652
+ .controller('DropdownController', ['$scope', '$attrs', '$parse', 'dropdownConfig', 'dropdownService', '$animate', function($scope, $attrs, $parse, dropdownConfig, dropdownService, $animate) {
1653
+ var self = this,
1654
+ scope = $scope.$new(), // create a child scope so we are not polluting original one
1655
+ openClass = dropdownConfig.openClass,
1656
+ getIsOpen,
1657
+ setIsOpen = angular.noop,
1658
+ toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop;
1659
+
1660
+ this.init = function( element ) {
1661
+ self.$element = element;
1662
+
1663
+ if ( $attrs.isOpen ) {
1664
+ getIsOpen = $parse($attrs.isOpen);
1665
+ setIsOpen = getIsOpen.assign;
1666
+
1667
+ $scope.$watch(getIsOpen, function(value) {
1668
+ scope.isOpen = !!value;
1669
+ });
1670
+ }
1671
+ };
1672
+
1673
+ this.toggle = function( open ) {
1674
+ return scope.isOpen = arguments.length ? !!open : !scope.isOpen;
1675
+ };
1676
+
1677
+ // Allow other directives to watch status
1678
+ this.isOpen = function() {
1679
+ return scope.isOpen;
1680
+ };
1681
+
1682
+ scope.focusToggleElement = function() {
1683
+ if ( self.toggleElement ) {
1684
+ self.toggleElement[0].focus();
1685
+ }
1686
+ };
1687
+
1688
+ scope.$watch('isOpen', function( isOpen, wasOpen ) {
1689
+ $animate[isOpen ? 'addClass' : 'removeClass'](self.$element, openClass);
1690
+
1691
+ if ( isOpen ) {
1692
+ scope.focusToggleElement();
1693
+ dropdownService.open( scope );
1694
+ } else {
1695
+ dropdownService.close( scope );
1696
+ }
1697
+
1698
+ setIsOpen($scope, isOpen);
1699
+ if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
1700
+ toggleInvoker($scope, { open: !!isOpen });
1701
+ }
1702
+ });
1703
+
1704
+ $scope.$on('$locationChangeSuccess', function() {
1705
+ scope.isOpen = false;
1706
+ });
1707
+
1708
+ $scope.$on('$destroy', function() {
1709
+ scope.$destroy();
1710
+ });
1711
+ }])
1712
+
1713
+ .directive('dropdown', function() {
1331
1714
  return {
1332
1715
  restrict: 'CA',
1333
- link: function(scope, element, attrs) {
1334
- scope.$watch('$location.path', function() { closeMenu(); });
1335
- element.parent().bind('click', function() { closeMenu(); });
1336
- element.bind('click', function (event) {
1716
+ controller: 'DropdownController',
1717
+ link: function(scope, element, attrs, dropdownCtrl) {
1718
+ dropdownCtrl.init( element );
1719
+ }
1720
+ };
1721
+ })
1337
1722
 
1338
- var elementWasOpen = (element === openElement);
1723
+ .directive('dropdownToggle', function() {
1724
+ return {
1725
+ restrict: 'CA',
1726
+ require: '?^dropdown',
1727
+ link: function(scope, element, attrs, dropdownCtrl) {
1728
+ if ( !dropdownCtrl ) {
1729
+ return;
1730
+ }
1731
+
1732
+ dropdownCtrl.toggleElement = element;
1339
1733
 
1734
+ var toggleDropdown = function(event) {
1340
1735
  event.preventDefault();
1341
- event.stopPropagation();
1342
1736
 
1343
- if (!!openElement) {
1344
- closeMenu();
1737
+ if ( !element.hasClass('disabled') && !attrs.disabled ) {
1738
+ scope.$apply(function() {
1739
+ dropdownCtrl.toggle();
1740
+ });
1345
1741
  }
1742
+ };
1346
1743
 
1347
- if (!elementWasOpen && !element.hasClass('disabled') && !element.prop('disabled')) {
1348
- element.parent().addClass('open');
1349
- openElement = element;
1350
- closeMenu = function (event) {
1351
- if (event) {
1352
- event.preventDefault();
1353
- event.stopPropagation();
1354
- }
1355
- $document.unbind('click', closeMenu);
1356
- element.parent().removeClass('open');
1357
- closeMenu = angular.noop;
1358
- openElement = null;
1359
- };
1360
- $document.bind('click', closeMenu);
1361
- }
1744
+ element.bind('click', toggleDropdown);
1745
+
1746
+ // WAI-ARIA
1747
+ element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
1748
+ scope.$watch(dropdownCtrl.isOpen, function( isOpen ) {
1749
+ element.attr('aria-expanded', !!isOpen);
1750
+ });
1751
+
1752
+ scope.$on('$destroy', function() {
1753
+ element.unbind('click', toggleDropdown);
1362
1754
  });
1363
1755
  }
1364
1756
  };
1365
- }]);
1757
+ });
1366
1758
 
1367
1759
  angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1368
1760
 
@@ -1449,9 +1841,12 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1449
1841
  },
1450
1842
  replace: true,
1451
1843
  transclude: true,
1452
- templateUrl: 'template/modal/window.html',
1844
+ templateUrl: function(tElement, tAttrs) {
1845
+ return tAttrs.templateUrl || 'template/modal/window.html';
1846
+ },
1453
1847
  link: function (scope, element, attrs) {
1454
- scope.windowClass = attrs.windowClass || '';
1848
+ element.addClass(attrs.windowClass || '');
1849
+ scope.size = attrs.size;
1455
1850
 
1456
1851
  $timeout(function () {
1457
1852
  // trigger CSS transitions
@@ -1507,8 +1902,11 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1507
1902
  openedWindows.remove(modalInstance);
1508
1903
 
1509
1904
  //remove window DOM element
1510
- removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, checkRemoveBackdrop);
1511
- body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
1905
+ removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, 300, function() {
1906
+ modalWindow.modalScope.$destroy();
1907
+ body.toggleClass(OPENED_MODAL_CLASS, openedWindows.length() > 0);
1908
+ checkRemoveBackdrop();
1909
+ });
1512
1910
  }
1513
1911
 
1514
1912
  function checkRemoveBackdrop() {
@@ -1562,8 +1960,9 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1562
1960
  if (evt.which === 27) {
1563
1961
  modal = openedWindows.top();
1564
1962
  if (modal && modal.value.keyboard) {
1963
+ evt.preventDefault();
1565
1964
  $rootScope.$apply(function () {
1566
- $modalStack.dismiss(modal.key);
1965
+ $modalStack.dismiss(modal.key, 'escape key press');
1567
1966
  });
1568
1967
  }
1569
1968
  }
@@ -1587,12 +1986,15 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1587
1986
  backdropDomEl = $compile('<div modal-backdrop></div>')(backdropScope);
1588
1987
  body.append(backdropDomEl);
1589
1988
  }
1590
-
1989
+
1591
1990
  var angularDomEl = angular.element('<div modal-window></div>');
1592
- angularDomEl.attr('window-class', modal.windowClass);
1593
- angularDomEl.attr('index', openedWindows.length() - 1);
1594
- angularDomEl.attr('animate', 'animate');
1595
- angularDomEl.html(modal.content);
1991
+ angularDomEl.attr({
1992
+ 'template-url': modal.windowTemplateUrl,
1993
+ 'window-class': modal.windowClass,
1994
+ 'size': modal.size,
1995
+ 'index': openedWindows.length() - 1,
1996
+ 'animate': 'animate'
1997
+ }).html(modal.content);
1596
1998
 
1597
1999
  var modalDomEl = $compile(angularDomEl)(modal.scope);
1598
2000
  openedWindows.top().value.modalDomEl = modalDomEl;
@@ -1716,7 +2118,9 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1716
2118
  content: tplAndVars[0],
1717
2119
  backdrop: modalOptions.backdrop,
1718
2120
  keyboard: modalOptions.keyboard,
1719
- windowClass: modalOptions.windowClass
2121
+ windowClass: modalOptions.windowClass,
2122
+ windowTemplateUrl: modalOptions.windowTemplateUrl,
2123
+ size: modalOptions.size
1720
2124
  });
1721
2125
 
1722
2126
  }, function resolveError(reason) {
@@ -1741,58 +2145,54 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.transition'])
1741
2145
 
1742
2146
  angular.module('ui.bootstrap.pagination', [])
1743
2147
 
1744
- .controller('PaginationController', ['$scope', '$attrs', '$parse', '$interpolate', function ($scope, $attrs, $parse, $interpolate) {
2148
+ .controller('PaginationController', ['$scope', '$attrs', '$parse', function ($scope, $attrs, $parse) {
1745
2149
  var self = this,
2150
+ ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
1746
2151
  setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
1747
2152
 
1748
- this.init = function(defaultItemsPerPage) {
2153
+ this.init = function(ngModelCtrl_, config) {
2154
+ ngModelCtrl = ngModelCtrl_;
2155
+ this.config = config;
2156
+
2157
+ ngModelCtrl.$render = function() {
2158
+ self.render();
2159
+ };
2160
+
1749
2161
  if ($attrs.itemsPerPage) {
1750
2162
  $scope.$parent.$watch($parse($attrs.itemsPerPage), function(value) {
1751
2163
  self.itemsPerPage = parseInt(value, 10);
1752
2164
  $scope.totalPages = self.calculateTotalPages();
1753
2165
  });
1754
2166
  } else {
1755
- this.itemsPerPage = defaultItemsPerPage;
2167
+ this.itemsPerPage = config.itemsPerPage;
1756
2168
  }
1757
2169
  };
1758
2170
 
1759
- this.noPrevious = function() {
1760
- return this.page === 1;
1761
- };
1762
- this.noNext = function() {
1763
- return this.page === $scope.totalPages;
1764
- };
1765
-
1766
- this.isActive = function(page) {
1767
- return this.page === page;
1768
- };
1769
-
1770
2171
  this.calculateTotalPages = function() {
1771
2172
  var totalPages = this.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / this.itemsPerPage);
1772
- return Math.max(totalPages || 0, 1);
1773
- };
1774
-
1775
- this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1776
- return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
2173
+ return Math.max(totalPages || 0, 1);
1777
2174
  };
1778
2175
 
1779
2176
  this.render = function() {
1780
- this.page = parseInt($scope.page, 10) || 1;
1781
- if (this.page > 0 && this.page <= $scope.totalPages) {
1782
- $scope.pages = this.getPages(this.page, $scope.totalPages);
1783
- }
2177
+ $scope.page = parseInt(ngModelCtrl.$viewValue, 10) || 1;
1784
2178
  };
1785
2179
 
1786
2180
  $scope.selectPage = function(page) {
1787
- if ( ! self.isActive(page) && page > 0 && page <= $scope.totalPages) {
1788
- $scope.page = page;
1789
- $scope.onSelectPage({ page: page });
2181
+ if ( $scope.page !== page && page > 0 && page <= $scope.totalPages) {
2182
+ ngModelCtrl.$setViewValue(page);
2183
+ ngModelCtrl.$render();
1790
2184
  }
1791
2185
  };
1792
2186
 
1793
- $scope.$watch('page', function() {
1794
- self.render();
1795
- });
2187
+ $scope.getText = function( key ) {
2188
+ return $scope[key + 'Text'] || self.config[key + 'Text'];
2189
+ };
2190
+ $scope.noPrevious = function() {
2191
+ return $scope.page === 1;
2192
+ };
2193
+ $scope.noNext = function() {
2194
+ return $scope.page === $scope.totalPages;
2195
+ };
1796
2196
 
1797
2197
  $scope.$watch('totalItems', function() {
1798
2198
  $scope.totalPages = self.calculateTotalPages();
@@ -1801,10 +2201,10 @@ angular.module('ui.bootstrap.pagination', [])
1801
2201
  $scope.$watch('totalPages', function(value) {
1802
2202
  setNumPages($scope.$parent, value); // Readonly variable
1803
2203
 
1804
- if ( self.page > value ) {
2204
+ if ( $scope.page > value ) {
1805
2205
  $scope.selectPage(value);
1806
2206
  } else {
1807
- self.render();
2207
+ ngModelCtrl.$render();
1808
2208
  }
1809
2209
  });
1810
2210
  }])
@@ -1820,30 +2220,34 @@ angular.module('ui.bootstrap.pagination', [])
1820
2220
  rotate: true
1821
2221
  })
1822
2222
 
1823
- .directive('pagination', ['$parse', 'paginationConfig', function($parse, config) {
2223
+ .directive('pagination', ['$parse', 'paginationConfig', function($parse, paginationConfig) {
1824
2224
  return {
1825
2225
  restrict: 'EA',
1826
2226
  scope: {
1827
- page: '=',
1828
2227
  totalItems: '=',
1829
- onSelectPage:' &'
2228
+ firstText: '@',
2229
+ previousText: '@',
2230
+ nextText: '@',
2231
+ lastText: '@'
1830
2232
  },
2233
+ require: ['pagination', '?ngModel'],
1831
2234
  controller: 'PaginationController',
1832
2235
  templateUrl: 'template/pagination/pagination.html',
1833
2236
  replace: true,
1834
- link: function(scope, element, attrs, paginationCtrl) {
2237
+ link: function(scope, element, attrs, ctrls) {
2238
+ var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2239
+
2240
+ if (!ngModelCtrl) {
2241
+ return; // do nothing if no ng-model
2242
+ }
1835
2243
 
1836
2244
  // Setup configuration parameters
1837
- var maxSize,
1838
- boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1839
- directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1840
- firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1841
- previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1842
- nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1843
- lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1844
- rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
2245
+ var maxSize = angular.isDefined(attrs.maxSize) ? scope.$parent.$eval(attrs.maxSize) : paginationConfig.maxSize,
2246
+ rotate = angular.isDefined(attrs.rotate) ? scope.$parent.$eval(attrs.rotate) : paginationConfig.rotate;
2247
+ scope.boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$parent.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
2248
+ scope.directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$parent.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
1845
2249
 
1846
- paginationCtrl.init(config.itemsPerPage);
2250
+ paginationCtrl.init(ngModelCtrl, paginationConfig);
1847
2251
 
1848
2252
  if (attrs.maxSize) {
1849
2253
  scope.$parent.$watch($parse(attrs.maxSize), function(value) {
@@ -1853,16 +2257,15 @@ angular.module('ui.bootstrap.pagination', [])
1853
2257
  }
1854
2258
 
1855
2259
  // Create page object used in template
1856
- function makePage(number, text, isActive, isDisabled) {
2260
+ function makePage(number, text, isActive) {
1857
2261
  return {
1858
2262
  number: number,
1859
2263
  text: text,
1860
- active: isActive,
1861
- disabled: isDisabled
2264
+ active: isActive
1862
2265
  };
1863
2266
  }
1864
2267
 
1865
- paginationCtrl.getPages = function(currentPage, totalPages) {
2268
+ function getPages(currentPage, totalPages) {
1866
2269
  var pages = [];
1867
2270
 
1868
2271
  // Default page limits
@@ -1892,42 +2295,32 @@ angular.module('ui.bootstrap.pagination', [])
1892
2295
 
1893
2296
  // Add page number links
1894
2297
  for (var number = startPage; number <= endPage; number++) {
1895
- var page = makePage(number, number, paginationCtrl.isActive(number), false);
2298
+ var page = makePage(number, number, number === currentPage);
1896
2299
  pages.push(page);
1897
2300
  }
1898
2301
 
1899
2302
  // Add links to move between page sets
1900
2303
  if ( isMaxSized && ! rotate ) {
1901
2304
  if ( startPage > 1 ) {
1902
- var previousPageSet = makePage(startPage - 1, '...', false, false);
2305
+ var previousPageSet = makePage(startPage - 1, '...', false);
1903
2306
  pages.unshift(previousPageSet);
1904
2307
  }
1905
2308
 
1906
2309
  if ( endPage < totalPages ) {
1907
- var nextPageSet = makePage(endPage + 1, '...', false, false);
2310
+ var nextPageSet = makePage(endPage + 1, '...', false);
1908
2311
  pages.push(nextPageSet);
1909
2312
  }
1910
2313
  }
1911
2314
 
1912
- // Add previous & next links
1913
- if (directionLinks) {
1914
- var previousPage = makePage(currentPage - 1, previousText, false, paginationCtrl.noPrevious());
1915
- pages.unshift(previousPage);
1916
-
1917
- var nextPage = makePage(currentPage + 1, nextText, false, paginationCtrl.noNext());
1918
- pages.push(nextPage);
1919
- }
1920
-
1921
- // Add first & last links
1922
- if (boundaryLinks) {
1923
- var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
1924
- pages.unshift(firstPage);
2315
+ return pages;
2316
+ }
1925
2317
 
1926
- var lastPage = makePage(totalPages, lastText, false, paginationCtrl.noNext());
1927
- pages.push(lastPage);
2318
+ var originalRender = paginationCtrl.render;
2319
+ paginationCtrl.render = function() {
2320
+ originalRender();
2321
+ if (scope.page > 0 && scope.page <= scope.totalPages) {
2322
+ scope.pages = getPages(scope.page, scope.totalPages);
1928
2323
  }
1929
-
1930
- return pages;
1931
2324
  };
1932
2325
  }
1933
2326
  };
@@ -1940,43 +2333,27 @@ angular.module('ui.bootstrap.pagination', [])
1940
2333
  align: true
1941
2334
  })
1942
2335
 
1943
- .directive('pager', ['pagerConfig', function(config) {
2336
+ .directive('pager', ['pagerConfig', function(pagerConfig) {
1944
2337
  return {
1945
2338
  restrict: 'EA',
1946
2339
  scope: {
1947
- page: '=',
1948
2340
  totalItems: '=',
1949
- onSelectPage:' &'
2341
+ previousText: '@',
2342
+ nextText: '@'
1950
2343
  },
2344
+ require: ['pager', '?ngModel'],
1951
2345
  controller: 'PaginationController',
1952
2346
  templateUrl: 'template/pagination/pager.html',
1953
2347
  replace: true,
1954
- link: function(scope, element, attrs, paginationCtrl) {
1955
-
1956
- // Setup configuration parameters
1957
- var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1958
- nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1959
- align = paginationCtrl.getAttributeValue(attrs.align, config.align);
1960
-
1961
- paginationCtrl.init(config.itemsPerPage);
2348
+ link: function(scope, element, attrs, ctrls) {
2349
+ var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
1962
2350
 
1963
- // Create page object used in template
1964
- function makePage(number, text, isDisabled, isPrevious, isNext) {
1965
- return {
1966
- number: number,
1967
- text: text,
1968
- disabled: isDisabled,
1969
- previous: ( align && isPrevious ),
1970
- next: ( align && isNext )
1971
- };
2351
+ if (!ngModelCtrl) {
2352
+ return; // do nothing if no ng-model
1972
2353
  }
1973
2354
 
1974
- paginationCtrl.getPages = function(currentPage) {
1975
- return [
1976
- makePage(currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false),
1977
- makePage(currentPage + 1, nextText, paginationCtrl.noNext(), false, true)
1978
- ];
1979
- };
2355
+ scope.align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : pagerConfig.align;
2356
+ paginationCtrl.init(ngModelCtrl, pagerConfig);
1980
2357
  }
1981
2358
  };
1982
2359
  }]);
@@ -2009,7 +2386,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2009
2386
 
2010
2387
  // The options specified to the provider globally.
2011
2388
  var globalOptions = {};
2012
-
2389
+
2013
2390
  /**
2014
2391
  * `options({})` allows global configuration of all tooltips in the
2015
2392
  * application.
@@ -2078,7 +2455,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2078
2455
 
2079
2456
  var startSym = $interpolate.startSymbol();
2080
2457
  var endSym = $interpolate.endSymbol();
2081
- var template =
2458
+ var template =
2082
2459
  '<div '+ directiveName +'-popup '+
2083
2460
  'title="'+startSym+'tt_title'+endSym+'" '+
2084
2461
  'content="'+startSym+'tt_content'+endSym+'" '+
@@ -2100,56 +2477,16 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2100
2477
  var popupTimeout;
2101
2478
  var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
2102
2479
  var triggers = getTriggers( undefined );
2103
- var hasRegisteredTriggers = false;
2104
2480
  var hasEnableExp = angular.isDefined(attrs[prefix+'Enable']);
2105
2481
 
2106
- var positionTooltip = function (){
2107
- var position,
2108
- ttWidth,
2109
- ttHeight,
2110
- ttPosition;
2111
- // Get the position of the directive element.
2112
- position = appendToBody ? $position.offset( element ) : $position.position( element );
2113
-
2114
- // Get the height and width of the tooltip so we can center it.
2115
- ttWidth = tooltip.prop( 'offsetWidth' );
2116
- ttHeight = tooltip.prop( 'offsetHeight' );
2117
-
2118
- // Calculate the tooltip's top and left coordinates to center it with
2119
- // this directive.
2120
- switch ( scope.tt_placement ) {
2121
- case 'right':
2122
- ttPosition = {
2123
- top: position.top + position.height / 2 - ttHeight / 2,
2124
- left: position.left + position.width
2125
- };
2126
- break;
2127
- case 'bottom':
2128
- ttPosition = {
2129
- top: position.top + position.height,
2130
- left: position.left + position.width / 2 - ttWidth / 2
2131
- };
2132
- break;
2133
- case 'left':
2134
- ttPosition = {
2135
- top: position.top + position.height / 2 - ttHeight / 2,
2136
- left: position.left - ttWidth
2137
- };
2138
- break;
2139
- default:
2140
- ttPosition = {
2141
- top: position.top - ttHeight,
2142
- left: position.left + position.width / 2 - ttWidth / 2
2143
- };
2144
- break;
2145
- }
2482
+ var positionTooltip = function () {
2146
2483
 
2484
+ var ttPosition = $position.positionElements(element, tooltip, scope.tt_placement, appendToBody);
2147
2485
  ttPosition.top += 'px';
2148
2486
  ttPosition.left += 'px';
2149
2487
 
2150
2488
  // Now set the calculated positioning.
2151
2489
  tooltip.css( ttPosition );
2152
-
2153
2490
  };
2154
2491
 
2155
2492
  // By default, the tooltip is not open.
@@ -2170,8 +2507,12 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2170
2507
  return;
2171
2508
  }
2172
2509
  if ( scope.tt_popupDelay ) {
2173
- popupTimeout = $timeout( show, scope.tt_popupDelay, false );
2174
- popupTimeout.then(function(reposition){reposition();});
2510
+ // Do nothing if the tooltip was already scheduled to pop-up.
2511
+ // This happens if show is triggered multiple times before any hide is triggered.
2512
+ if (!popupTimeout) {
2513
+ popupTimeout = $timeout( show, scope.tt_popupDelay, false );
2514
+ popupTimeout.then(function(reposition){reposition();});
2515
+ }
2175
2516
  } else {
2176
2517
  show()();
2177
2518
  }
@@ -2186,6 +2527,14 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2186
2527
  // Show the tooltip popup element.
2187
2528
  function show() {
2188
2529
 
2530
+ popupTimeout = null;
2531
+
2532
+ // If there is a pending remove transition, we must cancel it, lest the
2533
+ // tooltip be mysteriously removed.
2534
+ if ( transitionTimeout ) {
2535
+ $timeout.cancel( transitionTimeout );
2536
+ transitionTimeout = null;
2537
+ }
2189
2538
 
2190
2539
  // Don't show empty tooltips.
2191
2540
  if ( ! scope.tt_content ) {
@@ -2194,12 +2543,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2194
2543
 
2195
2544
  createTooltip();
2196
2545
 
2197
- // If there is a pending remove transition, we must cancel it, lest the
2198
- // tooltip be mysteriously removed.
2199
- if ( transitionTimeout ) {
2200
- $timeout.cancel( transitionTimeout );
2201
- }
2202
-
2203
2546
  // Set the initial positioning.
2204
2547
  tooltip.css({ top: 0, left: 0, display: 'block' });
2205
2548
 
@@ -2229,12 +2572,15 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2229
2572
 
2230
2573
  //if tooltip is going to be shown after delay, we must cancel this
2231
2574
  $timeout.cancel( popupTimeout );
2575
+ popupTimeout = null;
2232
2576
 
2233
2577
  // And now we remove it from the DOM. However, if we have animation, we
2234
2578
  // need to wait for it to expire beforehand.
2235
2579
  // FIXME: this is a placeholder for a port of the transitions library.
2236
2580
  if ( scope.tt_animation ) {
2237
- transitionTimeout = $timeout(removeTooltip, 500);
2581
+ if (!transitionTimeout) {
2582
+ transitionTimeout = $timeout(removeTooltip, 500);
2583
+ }
2238
2584
  } else {
2239
2585
  removeTooltip();
2240
2586
  }
@@ -2252,6 +2598,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2252
2598
  }
2253
2599
 
2254
2600
  function removeTooltip() {
2601
+ transitionTimeout = null;
2255
2602
  if (tooltip) {
2256
2603
  tooltip.remove();
2257
2604
  tooltip = null;
@@ -2282,11 +2629,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2282
2629
  scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
2283
2630
  });
2284
2631
 
2285
- var unregisterTriggers = function() {
2286
- if (hasRegisteredTriggers) {
2287
- element.unbind( triggers.show, showTooltipBind );
2288
- element.unbind( triggers.hide, hideTooltipBind );
2289
- }
2632
+ var unregisterTriggers = function () {
2633
+ element.unbind(triggers.show, showTooltipBind);
2634
+ element.unbind(triggers.hide, hideTooltipBind);
2290
2635
  };
2291
2636
 
2292
2637
  attrs.$observe( prefix+'Trigger', function ( val ) {
@@ -2300,8 +2645,6 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position', 'ui.bootstrap
2300
2645
  element.bind( triggers.show, showTooltipBind );
2301
2646
  element.bind( triggers.hide, hideTooltipBind );
2302
2647
  }
2303
-
2304
- hasRegisteredTriggers = true;
2305
2648
  });
2306
2649
 
2307
2650
  var animation = scope.$eval(attrs[prefix + 'Animation']);
@@ -2382,57 +2725,39 @@ angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
2382
2725
  return $tooltip( 'popover', 'popover', 'click' );
2383
2726
  }]);
2384
2727
 
2385
- angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2728
+ angular.module('ui.bootstrap.progressbar', [])
2386
2729
 
2387
2730
  .constant('progressConfig', {
2388
2731
  animate: true,
2389
2732
  max: 100
2390
2733
  })
2391
2734
 
2392
- .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', '$transition', function($scope, $attrs, progressConfig, $transition) {
2735
+ .controller('ProgressController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
2393
2736
  var self = this,
2394
- bars = [],
2395
- max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max,
2396
2737
  animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
2397
2738
 
2739
+ this.bars = [];
2740
+ $scope.max = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : progressConfig.max;
2741
+
2398
2742
  this.addBar = function(bar, element) {
2399
- var oldValue = 0, index = bar.$parent.$index;
2400
- if ( angular.isDefined(index) && bars[index] ) {
2401
- oldValue = bars[index].value;
2743
+ if ( !animate ) {
2744
+ element.css({'transition': 'none'});
2402
2745
  }
2403
- bars.push(bar);
2404
2746
 
2405
- this.update(element, bar.value, oldValue);
2747
+ this.bars.push(bar);
2406
2748
 
2407
- bar.$watch('value', function(value, oldValue) {
2408
- if (value !== oldValue) {
2409
- self.update(element, value, oldValue);
2410
- }
2749
+ bar.$watch('value', function( value ) {
2750
+ bar.percent = +(100 * value / $scope.max).toFixed(2);
2411
2751
  });
2412
2752
 
2413
2753
  bar.$on('$destroy', function() {
2754
+ element = null;
2414
2755
  self.removeBar(bar);
2415
2756
  });
2416
2757
  };
2417
2758
 
2418
- // Update bar element width
2419
- this.update = function(element, newValue, oldValue) {
2420
- var percent = this.getPercentage(newValue);
2421
-
2422
- if (animate) {
2423
- element.css('width', this.getPercentage(oldValue) + '%');
2424
- $transition(element, {width: percent + '%'});
2425
- } else {
2426
- element.css({'transition': 'none', 'width': percent + '%'});
2427
- }
2428
- };
2429
-
2430
2759
  this.removeBar = function(bar) {
2431
- bars.splice(bars.indexOf(bar), 1);
2432
- };
2433
-
2434
- this.getPercentage = function(value) {
2435
- return Math.round(100 * value / max);
2760
+ this.bars.splice(this.bars.indexOf(bar), 1);
2436
2761
  };
2437
2762
  }])
2438
2763
 
@@ -2444,8 +2769,7 @@ angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
2444
2769
  controller: 'ProgressController',
2445
2770
  require: 'progress',
2446
2771
  scope: {},
2447
- template: '<div class="progress" ng-transclude></div>'
2448
- //templateUrl: 'template/progressbar/progress.html' // Works in AngularJS 1.2
2772
+ templateUrl: 'template/progressbar/progress.html'
2449
2773
  };
2450
2774
  })
2451
2775
 
@@ -2490,68 +2814,79 @@ angular.module('ui.bootstrap.rating', [])
2490
2814
  stateOff: null
2491
2815
  })
2492
2816
 
2493
- .controller('RatingController', ['$scope', '$attrs', '$parse', 'ratingConfig', function($scope, $attrs, $parse, ratingConfig) {
2817
+ .controller('RatingController', ['$scope', '$attrs', 'ratingConfig', function($scope, $attrs, ratingConfig) {
2818
+ var ngModelCtrl = { $setViewValue: angular.noop };
2494
2819
 
2495
- this.maxRange = angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max;
2496
- this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
2497
- this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
2820
+ this.init = function(ngModelCtrl_) {
2821
+ ngModelCtrl = ngModelCtrl_;
2822
+ ngModelCtrl.$render = this.render;
2498
2823
 
2499
- this.createRateObjects = function(states) {
2500
- var defaultOptions = {
2501
- stateOn: this.stateOn,
2502
- stateOff: this.stateOff
2503
- };
2824
+ this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
2825
+ this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
2826
+
2827
+ var ratingStates = angular.isDefined($attrs.ratingStates) ? $scope.$parent.$eval($attrs.ratingStates) :
2828
+ new Array( angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max );
2829
+ $scope.range = this.buildTemplateObjects(ratingStates);
2830
+ };
2504
2831
 
2832
+ this.buildTemplateObjects = function(states) {
2505
2833
  for (var i = 0, n = states.length; i < n; i++) {
2506
- states[i] = angular.extend({ index: i }, defaultOptions, states[i]);
2834
+ states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff }, states[i]);
2507
2835
  }
2508
2836
  return states;
2509
2837
  };
2510
2838
 
2511
- // Get objects used in template
2512
- $scope.range = angular.isDefined($attrs.ratingStates) ? this.createRateObjects(angular.copy($scope.$parent.$eval($attrs.ratingStates))): this.createRateObjects(new Array(this.maxRange));
2513
-
2514
2839
  $scope.rate = function(value) {
2515
- if ( $scope.value !== value && !$scope.readonly ) {
2516
- $scope.value = value;
2840
+ if ( !$scope.readonly && value >= 0 && value <= $scope.range.length ) {
2841
+ ngModelCtrl.$setViewValue(value);
2842
+ ngModelCtrl.$render();
2517
2843
  }
2518
2844
  };
2519
2845
 
2520
2846
  $scope.enter = function(value) {
2521
- if ( ! $scope.readonly ) {
2522
- $scope.val = value;
2847
+ if ( !$scope.readonly ) {
2848
+ $scope.value = value;
2523
2849
  }
2524
2850
  $scope.onHover({value: value});
2525
2851
  };
2526
2852
 
2527
2853
  $scope.reset = function() {
2528
- $scope.val = angular.copy($scope.value);
2854
+ $scope.value = ngModelCtrl.$viewValue;
2529
2855
  $scope.onLeave();
2530
2856
  };
2531
2857
 
2532
- $scope.$watch('value', function(value) {
2533
- $scope.val = value;
2534
- });
2858
+ $scope.onKeydown = function(evt) {
2859
+ if (/(37|38|39|40)/.test(evt.which)) {
2860
+ evt.preventDefault();
2861
+ evt.stopPropagation();
2862
+ $scope.rate( $scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1) );
2863
+ }
2864
+ };
2535
2865
 
2536
- $scope.readonly = false;
2537
- if ($attrs.readonly) {
2538
- $scope.$parent.$watch($parse($attrs.readonly), function(value) {
2539
- $scope.readonly = !!value;
2540
- });
2541
- }
2866
+ this.render = function() {
2867
+ $scope.value = ngModelCtrl.$viewValue;
2868
+ };
2542
2869
  }])
2543
2870
 
2544
2871
  .directive('rating', function() {
2545
2872
  return {
2546
2873
  restrict: 'EA',
2874
+ require: ['rating', 'ngModel'],
2547
2875
  scope: {
2548
- value: '=',
2876
+ readonly: '=?',
2549
2877
  onHover: '&',
2550
2878
  onLeave: '&'
2551
2879
  },
2552
2880
  controller: 'RatingController',
2553
2881
  templateUrl: 'template/rating/rating.html',
2554
- replace: true
2882
+ replace: true,
2883
+ link: function(scope, element, attrs, ctrls) {
2884
+ var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
2885
+
2886
+ if ( ngModelCtrl ) {
2887
+ ratingCtrl.init( ngModelCtrl );
2888
+ }
2889
+ }
2555
2890
  };
2556
2891
  });
2557
2892
 
@@ -2569,16 +2904,24 @@ angular.module('ui.bootstrap.tabs', [])
2569
2904
  var ctrl = this,
2570
2905
  tabs = ctrl.tabs = $scope.tabs = [];
2571
2906
 
2572
- ctrl.select = function(tab) {
2907
+ ctrl.select = function(selectedTab) {
2573
2908
  angular.forEach(tabs, function(tab) {
2574
- tab.active = false;
2909
+ if (tab.active && tab !== selectedTab) {
2910
+ tab.active = false;
2911
+ tab.onDeselect();
2912
+ }
2575
2913
  });
2576
- tab.active = true;
2914
+ selectedTab.active = true;
2915
+ selectedTab.onSelect();
2577
2916
  };
2578
2917
 
2579
2918
  ctrl.addTab = function addTab(tab) {
2580
2919
  tabs.push(tab);
2581
- if (tabs.length === 1 || tab.active) {
2920
+ // we can't run the select function on the first tab
2921
+ // since that would select it twice
2922
+ if (tabs.length === 1) {
2923
+ tab.active = true;
2924
+ } else if (tab.active) {
2582
2925
  ctrl.select(tab);
2583
2926
  }
2584
2927
  };
@@ -2630,13 +2973,14 @@ angular.module('ui.bootstrap.tabs', [])
2630
2973
  restrict: 'EA',
2631
2974
  transclude: true,
2632
2975
  replace: true,
2633
- scope: {},
2976
+ scope: {
2977
+ type: '@'
2978
+ },
2634
2979
  controller: 'TabsetController',
2635
2980
  templateUrl: 'template/tabs/tabset.html',
2636
2981
  link: function(scope, element, attrs) {
2637
2982
  scope.vertical = angular.isDefined(attrs.vertical) ? scope.$parent.$eval(attrs.vertical) : false;
2638
2983
  scope.justified = angular.isDefined(attrs.justified) ? scope.$parent.$eval(attrs.justified) : false;
2639
- scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2640
2984
  }
2641
2985
  };
2642
2986
  })
@@ -2729,6 +3073,7 @@ angular.module('ui.bootstrap.tabs', [])
2729
3073
  templateUrl: 'template/tabs/tab.html',
2730
3074
  transclude: true,
2731
3075
  scope: {
3076
+ active: '=?',
2732
3077
  heading: '@',
2733
3078
  onSelect: '&select', //This callback is called in contentHeadingTransclude
2734
3079
  //once it inserts the tab's content into the dom
@@ -2739,32 +3084,9 @@ angular.module('ui.bootstrap.tabs', [])
2739
3084
  },
2740
3085
  compile: function(elm, attrs, transclude) {
2741
3086
  return function postLink(scope, elm, attrs, tabsetCtrl) {
2742
- var getActive, setActive;
2743
- if (attrs.active) {
2744
- getActive = $parse(attrs.active);
2745
- setActive = getActive.assign;
2746
- scope.$parent.$watch(getActive, function updateActive(value, oldVal) {
2747
- // Avoid re-initializing scope.active as it is already initialized
2748
- // below. (watcher is called async during init with value ===
2749
- // oldVal)
2750
- if (value !== oldVal) {
2751
- scope.active = !!value;
2752
- }
2753
- });
2754
- scope.active = getActive(scope.$parent);
2755
- } else {
2756
- setActive = getActive = angular.noop;
2757
- }
2758
-
2759
3087
  scope.$watch('active', function(active) {
2760
- // Note this watcher also initializes and assigns scope.active to the
2761
- // attrs.active expression.
2762
- setActive(scope.$parent, active);
2763
3088
  if (active) {
2764
3089
  tabsetCtrl.select(scope);
2765
- scope.onSelect();
2766
- } else {
2767
- scope.onDeselect();
2768
3090
  }
2769
3091
  });
2770
3092
 
@@ -2776,7 +3098,7 @@ angular.module('ui.bootstrap.tabs', [])
2776
3098
  }
2777
3099
 
2778
3100
  scope.select = function() {
2779
- if ( ! scope.disabled ) {
3101
+ if ( !scope.disabled ) {
2780
3102
  scope.active = true;
2781
3103
  }
2782
3104
  };
@@ -2786,7 +3108,6 @@ angular.module('ui.bootstrap.tabs', [])
2786
3108
  tabsetCtrl.removeTab(scope);
2787
3109
  });
2788
3110
 
2789
-
2790
3111
  //We need to transclude later, once the content container is ready.
2791
3112
  //when this link happens, we're inside a tab heading.
2792
3113
  scope.$transcludeFn = transclude;
@@ -2854,228 +3175,249 @@ angular.module('ui.bootstrap.timepicker', [])
2854
3175
  mousewheel: true
2855
3176
  })
2856
3177
 
2857
- .directive('timepicker', ['$parse', '$log', 'timepickerConfig', '$locale', function ($parse, $log, timepickerConfig, $locale) {
2858
- return {
2859
- restrict: 'EA',
2860
- require:'?^ngModel',
2861
- replace: true,
2862
- scope: {},
2863
- templateUrl: 'template/timepicker/timepicker.html',
2864
- link: function(scope, element, attrs, ngModel) {
2865
- if ( !ngModel ) {
2866
- return; // do nothing if no ng-model
2867
- }
3178
+ .controller('TimepickerController', ['$scope', '$attrs', '$parse', '$log', '$locale', 'timepickerConfig', function($scope, $attrs, $parse, $log, $locale, timepickerConfig) {
3179
+ var selected = new Date(),
3180
+ ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
3181
+ meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
2868
3182
 
2869
- var selected = new Date(),
2870
- meridians = angular.isDefined(attrs.meridians) ? scope.$parent.$eval(attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS;
3183
+ this.init = function( ngModelCtrl_, inputs ) {
3184
+ ngModelCtrl = ngModelCtrl_;
3185
+ ngModelCtrl.$render = this.render;
2871
3186
 
2872
- var hourStep = timepickerConfig.hourStep;
2873
- if (attrs.hourStep) {
2874
- scope.$parent.$watch($parse(attrs.hourStep), function(value) {
2875
- hourStep = parseInt(value, 10);
2876
- });
2877
- }
3187
+ var hoursInputEl = inputs.eq(0),
3188
+ minutesInputEl = inputs.eq(1);
2878
3189
 
2879
- var minuteStep = timepickerConfig.minuteStep;
2880
- if (attrs.minuteStep) {
2881
- scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
2882
- minuteStep = parseInt(value, 10);
2883
- });
2884
- }
3190
+ var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
3191
+ if ( mousewheel ) {
3192
+ this.setupMousewheelEvents( hoursInputEl, minutesInputEl );
3193
+ }
2885
3194
 
2886
- // 12H / 24H mode
2887
- scope.showMeridian = timepickerConfig.showMeridian;
2888
- if (attrs.showMeridian) {
2889
- scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
2890
- scope.showMeridian = !!value;
2891
-
2892
- if ( ngModel.$error.time ) {
2893
- // Evaluate from template
2894
- var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
2895
- if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
2896
- selected.setHours( hours );
2897
- refresh();
2898
- }
2899
- } else {
2900
- updateTemplate();
2901
- }
2902
- });
2903
- }
3195
+ $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
3196
+ this.setupInputEvents( hoursInputEl, minutesInputEl );
3197
+ };
2904
3198
 
2905
- // Get scope.hours in 24H mode if valid
2906
- function getHoursFromTemplate ( ) {
2907
- var hours = parseInt( scope.hours, 10 );
2908
- var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
2909
- if ( !valid ) {
2910
- return undefined;
2911
- }
3199
+ var hourStep = timepickerConfig.hourStep;
3200
+ if ($attrs.hourStep) {
3201
+ $scope.$parent.$watch($parse($attrs.hourStep), function(value) {
3202
+ hourStep = parseInt(value, 10);
3203
+ });
3204
+ }
2912
3205
 
2913
- if ( scope.showMeridian ) {
2914
- if ( hours === 12 ) {
2915
- hours = 0;
2916
- }
2917
- if ( scope.meridian === meridians[1] ) {
2918
- hours = hours + 12;
2919
- }
3206
+ var minuteStep = timepickerConfig.minuteStep;
3207
+ if ($attrs.minuteStep) {
3208
+ $scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
3209
+ minuteStep = parseInt(value, 10);
3210
+ });
3211
+ }
3212
+
3213
+ // 12H / 24H mode
3214
+ $scope.showMeridian = timepickerConfig.showMeridian;
3215
+ if ($attrs.showMeridian) {
3216
+ $scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
3217
+ $scope.showMeridian = !!value;
3218
+
3219
+ if ( ngModelCtrl.$error.time ) {
3220
+ // Evaluate from template
3221
+ var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
3222
+ if (angular.isDefined( hours ) && angular.isDefined( minutes )) {
3223
+ selected.setHours( hours );
3224
+ refresh();
2920
3225
  }
2921
- return hours;
3226
+ } else {
3227
+ updateTemplate();
2922
3228
  }
3229
+ });
3230
+ }
2923
3231
 
2924
- function getMinutesFromTemplate() {
2925
- var minutes = parseInt(scope.minutes, 10);
2926
- return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
2927
- }
3232
+ // Get $scope.hours in 24H mode if valid
3233
+ function getHoursFromTemplate ( ) {
3234
+ var hours = parseInt( $scope.hours, 10 );
3235
+ var valid = ( $scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
3236
+ if ( !valid ) {
3237
+ return undefined;
3238
+ }
2928
3239
 
2929
- function pad( value ) {
2930
- return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
3240
+ if ( $scope.showMeridian ) {
3241
+ if ( hours === 12 ) {
3242
+ hours = 0;
2931
3243
  }
3244
+ if ( $scope.meridian === meridians[1] ) {
3245
+ hours = hours + 12;
3246
+ }
3247
+ }
3248
+ return hours;
3249
+ }
2932
3250
 
2933
- // Input elements
2934
- var inputs = element.find('input'), hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
3251
+ function getMinutesFromTemplate() {
3252
+ var minutes = parseInt($scope.minutes, 10);
3253
+ return ( minutes >= 0 && minutes < 60 ) ? minutes : undefined;
3254
+ }
2935
3255
 
2936
- // Respond on mousewheel spin
2937
- var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2938
- if ( mousewheel ) {
3256
+ function pad( value ) {
3257
+ return ( angular.isDefined(value) && value.toString().length < 2 ) ? '0' + value : value;
3258
+ }
2939
3259
 
2940
- var isScrollingUp = function(e) {
2941
- if (e.originalEvent) {
2942
- e = e.originalEvent;
2943
- }
2944
- //pick correct delta variable depending on event
2945
- var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2946
- return (e.detail || delta > 0);
2947
- };
3260
+ // Respond on mousewheel spin
3261
+ this.setupMousewheelEvents = function( hoursInputEl, minutesInputEl ) {
3262
+ var isScrollingUp = function(e) {
3263
+ if (e.originalEvent) {
3264
+ e = e.originalEvent;
3265
+ }
3266
+ //pick correct delta variable depending on event
3267
+ var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
3268
+ return (e.detail || delta > 0);
3269
+ };
2948
3270
 
2949
- hoursInputEl.bind('mousewheel wheel', function(e) {
2950
- scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2951
- e.preventDefault();
2952
- });
3271
+ hoursInputEl.bind('mousewheel wheel', function(e) {
3272
+ $scope.$apply( (isScrollingUp(e)) ? $scope.incrementHours() : $scope.decrementHours() );
3273
+ e.preventDefault();
3274
+ });
2953
3275
 
2954
- minutesInputEl.bind('mousewheel wheel', function(e) {
2955
- scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
2956
- e.preventDefault();
2957
- });
2958
- }
3276
+ minutesInputEl.bind('mousewheel wheel', function(e) {
3277
+ $scope.$apply( (isScrollingUp(e)) ? $scope.incrementMinutes() : $scope.decrementMinutes() );
3278
+ e.preventDefault();
3279
+ });
2959
3280
 
2960
- scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
2961
- if ( ! scope.readonlyInput ) {
3281
+ };
2962
3282
 
2963
- var invalidate = function(invalidHours, invalidMinutes) {
2964
- ngModel.$setViewValue( null );
2965
- ngModel.$setValidity('time', false);
2966
- if (angular.isDefined(invalidHours)) {
2967
- scope.invalidHours = invalidHours;
2968
- }
2969
- if (angular.isDefined(invalidMinutes)) {
2970
- scope.invalidMinutes = invalidMinutes;
2971
- }
2972
- };
3283
+ this.setupInputEvents = function( hoursInputEl, minutesInputEl ) {
3284
+ if ( $scope.readonlyInput ) {
3285
+ $scope.updateHours = angular.noop;
3286
+ $scope.updateMinutes = angular.noop;
3287
+ return;
3288
+ }
2973
3289
 
2974
- scope.updateHours = function() {
2975
- var hours = getHoursFromTemplate();
3290
+ var invalidate = function(invalidHours, invalidMinutes) {
3291
+ ngModelCtrl.$setViewValue( null );
3292
+ ngModelCtrl.$setValidity('time', false);
3293
+ if (angular.isDefined(invalidHours)) {
3294
+ $scope.invalidHours = invalidHours;
3295
+ }
3296
+ if (angular.isDefined(invalidMinutes)) {
3297
+ $scope.invalidMinutes = invalidMinutes;
3298
+ }
3299
+ };
2976
3300
 
2977
- if ( angular.isDefined(hours) ) {
2978
- selected.setHours( hours );
2979
- refresh( 'h' );
2980
- } else {
2981
- invalidate(true);
2982
- }
2983
- };
3301
+ $scope.updateHours = function() {
3302
+ var hours = getHoursFromTemplate();
2984
3303
 
2985
- hoursInputEl.bind('blur', function(e) {
2986
- if ( !scope.validHours && scope.hours < 10) {
2987
- scope.$apply( function() {
2988
- scope.hours = pad( scope.hours );
2989
- });
2990
- }
3304
+ if ( angular.isDefined(hours) ) {
3305
+ selected.setHours( hours );
3306
+ refresh( 'h' );
3307
+ } else {
3308
+ invalidate(true);
3309
+ }
3310
+ };
3311
+
3312
+ hoursInputEl.bind('blur', function(e) {
3313
+ if ( !$scope.invalidHours && $scope.hours < 10) {
3314
+ $scope.$apply( function() {
3315
+ $scope.hours = pad( $scope.hours );
2991
3316
  });
3317
+ }
3318
+ });
2992
3319
 
2993
- scope.updateMinutes = function() {
2994
- var minutes = getMinutesFromTemplate();
3320
+ $scope.updateMinutes = function() {
3321
+ var minutes = getMinutesFromTemplate();
2995
3322
 
2996
- if ( angular.isDefined(minutes) ) {
2997
- selected.setMinutes( minutes );
2998
- refresh( 'm' );
2999
- } else {
3000
- invalidate(undefined, true);
3001
- }
3002
- };
3323
+ if ( angular.isDefined(minutes) ) {
3324
+ selected.setMinutes( minutes );
3325
+ refresh( 'm' );
3326
+ } else {
3327
+ invalidate(undefined, true);
3328
+ }
3329
+ };
3003
3330
 
3004
- minutesInputEl.bind('blur', function(e) {
3005
- if ( !scope.invalidMinutes && scope.minutes < 10 ) {
3006
- scope.$apply( function() {
3007
- scope.minutes = pad( scope.minutes );
3008
- });
3009
- }
3331
+ minutesInputEl.bind('blur', function(e) {
3332
+ if ( !$scope.invalidMinutes && $scope.minutes < 10 ) {
3333
+ $scope.$apply( function() {
3334
+ $scope.minutes = pad( $scope.minutes );
3010
3335
  });
3011
- } else {
3012
- scope.updateHours = angular.noop;
3013
- scope.updateMinutes = angular.noop;
3014
3336
  }
3337
+ });
3015
3338
 
3016
- ngModel.$render = function() {
3017
- var date = ngModel.$modelValue ? new Date( ngModel.$modelValue ) : null;
3339
+ };
3018
3340
 
3019
- if ( isNaN(date) ) {
3020
- ngModel.$setValidity('time', false);
3021
- $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.');
3022
- } else {
3023
- if ( date ) {
3024
- selected = date;
3025
- }
3026
- makeValid();
3027
- updateTemplate();
3028
- }
3029
- };
3341
+ this.render = function() {
3342
+ var date = ngModelCtrl.$modelValue ? new Date( ngModelCtrl.$modelValue ) : null;
3030
3343
 
3031
- // Call internally when we know that model is valid.
3032
- function refresh( keyboardChange ) {
3033
- makeValid();
3034
- ngModel.$setViewValue( new Date(selected) );
3035
- updateTemplate( keyboardChange );
3344
+ if ( isNaN(date) ) {
3345
+ ngModelCtrl.$setValidity('time', false);
3346
+ $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.');
3347
+ } else {
3348
+ if ( date ) {
3349
+ selected = date;
3036
3350
  }
3351
+ makeValid();
3352
+ updateTemplate();
3353
+ }
3354
+ };
3037
3355
 
3038
- function makeValid() {
3039
- ngModel.$setValidity('time', true);
3040
- scope.invalidHours = false;
3041
- scope.invalidMinutes = false;
3042
- }
3356
+ // Call internally when we know that model is valid.
3357
+ function refresh( keyboardChange ) {
3358
+ makeValid();
3359
+ ngModelCtrl.$setViewValue( new Date(selected) );
3360
+ updateTemplate( keyboardChange );
3361
+ }
3043
3362
 
3044
- function updateTemplate( keyboardChange ) {
3045
- var hours = selected.getHours(), minutes = selected.getMinutes();
3363
+ function makeValid() {
3364
+ ngModelCtrl.$setValidity('time', true);
3365
+ $scope.invalidHours = false;
3366
+ $scope.invalidMinutes = false;
3367
+ }
3046
3368
 
3047
- if ( scope.showMeridian ) {
3048
- hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
3049
- }
3050
- scope.hours = keyboardChange === 'h' ? hours : pad(hours);
3051
- scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
3052
- scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
3053
- }
3369
+ function updateTemplate( keyboardChange ) {
3370
+ var hours = selected.getHours(), minutes = selected.getMinutes();
3054
3371
 
3055
- function addMinutes( minutes ) {
3056
- var dt = new Date( selected.getTime() + minutes * 60000 );
3057
- selected.setHours( dt.getHours(), dt.getMinutes() );
3058
- refresh();
3059
- }
3372
+ if ( $scope.showMeridian ) {
3373
+ hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12; // Convert 24 to 12 hour system
3374
+ }
3060
3375
 
3061
- scope.incrementHours = function() {
3062
- addMinutes( hourStep * 60 );
3063
- };
3064
- scope.decrementHours = function() {
3065
- addMinutes( - hourStep * 60 );
3066
- };
3067
- scope.incrementMinutes = function() {
3068
- addMinutes( minuteStep );
3069
- };
3070
- scope.decrementMinutes = function() {
3071
- addMinutes( - minuteStep );
3072
- };
3073
- scope.toggleMeridian = function() {
3074
- addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
3075
- };
3376
+ $scope.hours = keyboardChange === 'h' ? hours : pad(hours);
3377
+ $scope.minutes = keyboardChange === 'm' ? minutes : pad(minutes);
3378
+ $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
3379
+ }
3380
+
3381
+ function addMinutes( minutes ) {
3382
+ var dt = new Date( selected.getTime() + minutes * 60000 );
3383
+ selected.setHours( dt.getHours(), dt.getMinutes() );
3384
+ refresh();
3385
+ }
3386
+
3387
+ $scope.incrementHours = function() {
3388
+ addMinutes( hourStep * 60 );
3389
+ };
3390
+ $scope.decrementHours = function() {
3391
+ addMinutes( - hourStep * 60 );
3392
+ };
3393
+ $scope.incrementMinutes = function() {
3394
+ addMinutes( minuteStep );
3395
+ };
3396
+ $scope.decrementMinutes = function() {
3397
+ addMinutes( - minuteStep );
3398
+ };
3399
+ $scope.toggleMeridian = function() {
3400
+ addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
3401
+ };
3402
+ }])
3403
+
3404
+ .directive('timepicker', function () {
3405
+ return {
3406
+ restrict: 'EA',
3407
+ require: ['timepicker', '?^ngModel'],
3408
+ controller:'TimepickerController',
3409
+ replace: true,
3410
+ scope: {},
3411
+ templateUrl: 'template/timepicker/timepicker.html',
3412
+ link: function(scope, element, attrs, ctrls) {
3413
+ var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
3414
+
3415
+ if ( ngModelCtrl ) {
3416
+ timepickerCtrl.init( ngModelCtrl, element.find('input') );
3417
+ }
3076
3418
  }
3077
3419
  };
3078
- }]);
3420
+ });
3079
3421
 
3080
3422
  angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap.bindHtml'])
3081
3423
 
@@ -3091,11 +3433,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3091
3433
  return {
3092
3434
  parse:function (input) {
3093
3435
 
3094
- var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
3436
+ var match = input.match(TYPEAHEAD_REGEXP);
3095
3437
  if (!match) {
3096
3438
  throw new Error(
3097
- "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
3098
- " but got '" + input + "'.");
3439
+ 'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
3440
+ ' but got "' + input + '".');
3099
3441
  }
3100
3442
 
3101
3443
  return {
@@ -3136,7 +3478,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3136
3478
 
3137
3479
  var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
3138
3480
 
3139
- var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;
3481
+ var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
3140
3482
 
3141
3483
  //INTERNAL VARIABLES
3142
3484
 
@@ -3148,9 +3490,25 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3148
3490
 
3149
3491
  var hasFocus;
3150
3492
 
3493
+ //create a child scope for the typeahead directive so we are not polluting original scope
3494
+ //with typeahead-specific data (matches, query etc.)
3495
+ var scope = originalScope.$new();
3496
+ originalScope.$on('$destroy', function(){
3497
+ scope.$destroy();
3498
+ });
3499
+
3500
+ // WAI-ARIA
3501
+ var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
3502
+ element.attr({
3503
+ 'aria-autocomplete': 'list',
3504
+ 'aria-expanded': false,
3505
+ 'aria-owns': popupId
3506
+ });
3507
+
3151
3508
  //pop-up element used to display matches
3152
3509
  var popUpEl = angular.element('<div typeahead-popup></div>');
3153
3510
  popUpEl.attr({
3511
+ id: popupId,
3154
3512
  matches: 'matches',
3155
3513
  active: 'activeIdx',
3156
3514
  select: 'select(activeIdx)',
@@ -3162,18 +3520,26 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3162
3520
  popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
3163
3521
  }
3164
3522
 
3165
- //create a child scope for the typeahead directive so we are not polluting original scope
3166
- //with typeahead-specific data (matches, query etc.)
3167
- var scope = originalScope.$new();
3168
- originalScope.$on('$destroy', function(){
3169
- scope.$destroy();
3170
- });
3171
-
3172
3523
  var resetMatches = function() {
3173
3524
  scope.matches = [];
3174
3525
  scope.activeIdx = -1;
3526
+ element.attr('aria-expanded', false);
3175
3527
  };
3176
3528
 
3529
+ var getMatchId = function(index) {
3530
+ return popupId + '-option-' + index;
3531
+ };
3532
+
3533
+ // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
3534
+ // This attribute is added or removed automatically when the `activeIdx` changes.
3535
+ scope.$watch('activeIdx', function(index) {
3536
+ if (index < 0) {
3537
+ element.removeAttr('aria-activedescendant');
3538
+ } else {
3539
+ element.attr('aria-activedescendant', getMatchId(index));
3540
+ }
3541
+ });
3542
+
3177
3543
  var getMatchesAsync = function(inputValue) {
3178
3544
 
3179
3545
  var locals = {$viewValue: inputValue};
@@ -3182,7 +3548,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3182
3548
 
3183
3549
  //it might happen that several async queries were in progress if a user were typing fast
3184
3550
  //but we are interested only in responses that correspond to the current view value
3185
- if (inputValue === modelCtrl.$viewValue && hasFocus) {
3551
+ var onCurrentRequest = (inputValue === modelCtrl.$viewValue);
3552
+ if (onCurrentRequest && hasFocus) {
3186
3553
  if (matches.length > 0) {
3187
3554
 
3188
3555
  scope.activeIdx = 0;
@@ -3192,6 +3559,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3192
3559
  for(var i=0; i<matches.length; i++) {
3193
3560
  locals[parserResult.itemName] = matches[i];
3194
3561
  scope.matches.push({
3562
+ id: getMatchId(i),
3195
3563
  label: parserResult.viewMapper(scope, locals),
3196
3564
  model: matches[i]
3197
3565
  });
@@ -3204,9 +3572,12 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3204
3572
  scope.position = appendToBody ? $position.offset(element) : $position.position(element);
3205
3573
  scope.position.top = scope.position.top + element.prop('offsetHeight');
3206
3574
 
3575
+ element.attr('aria-expanded', true);
3207
3576
  } else {
3208
3577
  resetMatches();
3209
3578
  }
3579
+ }
3580
+ if (onCurrentRequest) {
3210
3581
  isLoadingSetter(originalScope, false);
3211
3582
  }
3212
3583
  }, function(){
@@ -3300,8 +3671,9 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3300
3671
 
3301
3672
  resetMatches();
3302
3673
 
3303
- //return focus to the input element if a mach was selected via a mouse click event
3304
- element[0].focus();
3674
+ //return focus to the input element if a match was selected via a mouse click event
3675
+ // use timeout to avoid $rootScope:inprog error
3676
+ $timeout(function() { element[0].focus(); }, 0, false);
3305
3677
  };
3306
3678
 
3307
3679
  //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
@@ -3419,19 +3791,20 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
3419
3791
  .filter('typeaheadHighlight', function() {
3420
3792
 
3421
3793
  function escapeRegexp(queryToEscape) {
3422
- return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
3794
+ return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
3423
3795
  }
3424
3796
 
3425
3797
  return function(matchItem, query) {
3426
- return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3798
+ return query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem;
3427
3799
  };
3428
3800
  });
3801
+
3429
3802
  angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache) {
3430
3803
  $templateCache.put("template/accordion/accordion-group.html",
3431
3804
  "<div class=\"panel panel-default\">\n" +
3432
3805
  " <div class=\"panel-heading\">\n" +
3433
3806
  " <h4 class=\"panel-title\">\n" +
3434
- " <a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a>\n" +
3807
+ " <a class=\"accordion-toggle\" ng-click=\"toggleOpen()\" accordion-transclude=\"heading\"><span ng-class=\"{'text-muted': isDisabled}\">{{heading}}</span></a>\n" +
3435
3808
  " </h4>\n" +
3436
3809
  " </div>\n" +
3437
3810
  " <div class=\"panel-collapse\" collapse=\"!isOpen\">\n" +
@@ -3447,8 +3820,11 @@ angular.module("template/accordion/accordion.html", []).run(["$templateCache", f
3447
3820
 
3448
3821
  angular.module("template/alert/alert.html", []).run(["$templateCache", function($templateCache) {
3449
3822
  $templateCache.put("template/alert/alert.html",
3450
- "<div class='alert' ng-class='\"alert-\" + (type || \"warning\")'>\n" +
3451
- " <button ng-show='closeable' type='button' class='close' ng-click='close()'>&times;</button>\n" +
3823
+ "<div class=\"alert\" ng-class=\"{'alert-{{type || 'warning'}}': true, 'alert-dismissable': closeable}\" role=\"alert\">\n" +
3824
+ " <button ng-show=\"closeable\" type=\"button\" class=\"close\" ng-click=\"close()\">\n" +
3825
+ " <span aria-hidden=\"true\">&times;</span>\n" +
3826
+ " <span class=\"sr-only\">Close</span>\n" +
3827
+ " </button>\n" +
3452
3828
  " <div ng-transclude></div>\n" +
3453
3829
  "</div>\n" +
3454
3830
  "");
@@ -3456,13 +3832,13 @@ angular.module("template/alert/alert.html", []).run(["$templateCache", function(
3456
3832
 
3457
3833
  angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache) {
3458
3834
  $templateCache.put("template/carousel/carousel.html",
3459
- "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\">\n" +
3460
- " <ol class=\"carousel-indicators\" ng-show=\"slides().length > 1\">\n" +
3461
- " <li ng-repeat=\"slide in slides()\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
3835
+ "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\" ng-swipe-right=\"prev()\" ng-swipe-left=\"next()\">\n" +
3836
+ " <ol class=\"carousel-indicators\" ng-show=\"slides.length > 1\">\n" +
3837
+ " <li ng-repeat=\"slide in slides track by $index\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>\n" +
3462
3838
  " </ol>\n" +
3463
3839
  " <div class=\"carousel-inner\" ng-transclude></div>\n" +
3464
- " <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides().length > 1\"><span class=\"icon-prev\"></span></a>\n" +
3465
- " <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides().length > 1\"><span class=\"icon-next\"></span></a>\n" +
3840
+ " <a class=\"left carousel-control\" ng-click=\"prev()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-left\"></span></a>\n" +
3841
+ " <a class=\"right carousel-control\" ng-click=\"next()\" ng-show=\"slides.length > 1\"><span class=\"glyphicon glyphicon-chevron-right\"></span></a>\n" +
3466
3842
  "</div>\n" +
3467
3843
  "");
3468
3844
  }]);
@@ -3481,23 +3857,53 @@ angular.module("template/carousel/slide.html", []).run(["$templateCache", functi
3481
3857
 
3482
3858
  angular.module("template/datepicker/datepicker.html", []).run(["$templateCache", function($templateCache) {
3483
3859
  $templateCache.put("template/datepicker/datepicker.html",
3484
- "<table>\n" +
3860
+ "<div ng-switch=\"datepickerMode\" role=\"application\" ng-keydown=\"keydown($event)\">\n" +
3861
+ " <daypicker ng-switch-when=\"day\" tabindex=\"0\"></daypicker>\n" +
3862
+ " <monthpicker ng-switch-when=\"month\" tabindex=\"0\"></monthpicker>\n" +
3863
+ " <yearpicker ng-switch-when=\"year\" tabindex=\"0\"></yearpicker>\n" +
3864
+ "</div>");
3865
+ }]);
3866
+
3867
+ angular.module("template/datepicker/day.html", []).run(["$templateCache", function($templateCache) {
3868
+ $templateCache.put("template/datepicker/day.html",
3869
+ "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
3485
3870
  " <thead>\n" +
3486
3871
  " <tr>\n" +
3487
- " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
3488
- " <th colspan=\"{{rows[0].length - 2 + showWeekNumbers}}\"><button type=\"button\" class=\"btn btn-default btn-sm btn-block\" ng-click=\"toggleMode()\"><strong>{{title}}</strong></button></th>\n" +
3489
- " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
3872
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
3873
+ " <th colspan=\"{{5 + showWeeks}}\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
3874
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
3875
+ " </tr>\n" +
3876
+ " <tr>\n" +
3877
+ " <th ng-show=\"showWeeks\" class=\"text-center\"></th>\n" +
3878
+ " <th ng-repeat=\"label in labels track by $index\" class=\"text-center\"><small aria-label=\"{{label.full}}\">{{label.abbr}}</small></th>\n" +
3879
+ " </tr>\n" +
3880
+ " </thead>\n" +
3881
+ " <tbody>\n" +
3882
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
3883
+ " <td ng-show=\"showWeeks\" class=\"text-center h6\"><em>{{ weekNumbers[$index] }}</em></td>\n" +
3884
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
3885
+ " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-muted': dt.secondary, 'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
3886
+ " </td>\n" +
3490
3887
  " </tr>\n" +
3491
- " <tr ng-show=\"labels.length > 0\" class=\"h6\">\n" +
3492
- " <th ng-show=\"showWeekNumbers\" class=\"text-center\">#</th>\n" +
3493
- " <th ng-repeat=\"label in labels\" class=\"text-center\">{{label}}</th>\n" +
3888
+ " </tbody>\n" +
3889
+ "</table>\n" +
3890
+ "");
3891
+ }]);
3892
+
3893
+ angular.module("template/datepicker/month.html", []).run(["$templateCache", function($templateCache) {
3894
+ $templateCache.put("template/datepicker/month.html",
3895
+ "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
3896
+ " <thead>\n" +
3897
+ " <tr>\n" +
3898
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
3899
+ " <th><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
3900
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
3494
3901
  " </tr>\n" +
3495
3902
  " </thead>\n" +
3496
3903
  " <tbody>\n" +
3497
- " <tr ng-repeat=\"row in rows\">\n" +
3498
- " <td ng-show=\"showWeekNumbers\" class=\"text-center\"><em>{{ getWeekNumber(row) }}</em></td>\n" +
3499
- " <td ng-repeat=\"dt in row\" class=\"text-center\">\n" +
3500
- " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default btn-sm\" ng-class=\"{'btn-info': dt.selected}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\"><span ng-class=\"{'text-muted': dt.secondary}\">{{dt.label}}</span></button>\n" +
3904
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
3905
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
3906
+ " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
3501
3907
  " </td>\n" +
3502
3908
  " </tr>\n" +
3503
3909
  " </tbody>\n" +
@@ -3507,43 +3913,72 @@ angular.module("template/datepicker/datepicker.html", []).run(["$templateCache",
3507
3913
 
3508
3914
  angular.module("template/datepicker/popup.html", []).run(["$templateCache", function($templateCache) {
3509
3915
  $templateCache.put("template/datepicker/popup.html",
3510
- "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3916
+ "<ul class=\"dropdown-menu\" ng-style=\"{display: (isOpen && 'block') || 'none', top: position.top+'px', left: position.left+'px'}\" ng-keydown=\"keydown($event)\">\n" +
3511
3917
  " <li ng-transclude></li>\n" +
3512
- " <li ng-show=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
3918
+ " <li ng-if=\"showButtonBar\" style=\"padding:10px 9px 2px\">\n" +
3513
3919
  " <span class=\"btn-group\">\n" +
3514
- " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"today()\">{{currentText}}</button>\n" +
3515
- " <button type=\"button\" class=\"btn btn-sm btn-default\" ng-click=\"showWeeks = ! showWeeks\" ng-class=\"{active: showWeeks}\">{{toggleWeeksText}}</button>\n" +
3516
- " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"clear()\">{{clearText}}</button>\n" +
3920
+ " <button type=\"button\" class=\"btn btn-sm btn-info\" ng-click=\"select('today')\">{{ getText('current') }}</button>\n" +
3921
+ " <button type=\"button\" class=\"btn btn-sm btn-danger\" ng-click=\"select(null)\">{{ getText('clear') }}</button>\n" +
3517
3922
  " </span>\n" +
3518
- " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"isOpen = false\">{{closeText}}</button>\n" +
3923
+ " <button type=\"button\" class=\"btn btn-sm btn-success pull-right\" ng-click=\"close()\">{{ getText('close') }}</button>\n" +
3519
3924
  " </li>\n" +
3520
3925
  "</ul>\n" +
3521
3926
  "");
3522
3927
  }]);
3523
3928
 
3929
+ angular.module("template/datepicker/year.html", []).run(["$templateCache", function($templateCache) {
3930
+ $templateCache.put("template/datepicker/year.html",
3931
+ "<table role=\"grid\" aria-labelledby=\"{{uniqueId}}-title\" aria-activedescendant=\"{{activeDateId}}\">\n" +
3932
+ " <thead>\n" +
3933
+ " <tr>\n" +
3934
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-left\" ng-click=\"move(-1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-left\"></i></button></th>\n" +
3935
+ " <th colspan=\"3\"><button id=\"{{uniqueId}}-title\" role=\"heading\" aria-live=\"assertive\" aria-atomic=\"true\" type=\"button\" class=\"btn btn-default btn-sm\" ng-click=\"toggleMode()\" tabindex=\"-1\" style=\"width:100%;\"><strong>{{title}}</strong></button></th>\n" +
3936
+ " <th><button type=\"button\" class=\"btn btn-default btn-sm pull-right\" ng-click=\"move(1)\" tabindex=\"-1\"><i class=\"glyphicon glyphicon-chevron-right\"></i></button></th>\n" +
3937
+ " </tr>\n" +
3938
+ " </thead>\n" +
3939
+ " <tbody>\n" +
3940
+ " <tr ng-repeat=\"row in rows track by $index\">\n" +
3941
+ " <td ng-repeat=\"dt in row track by dt.date\" class=\"text-center\" role=\"gridcell\" id=\"{{dt.uid}}\" aria-disabled=\"{{!!dt.disabled}}\">\n" +
3942
+ " <button type=\"button\" style=\"width:100%;\" class=\"btn btn-default\" ng-class=\"{'btn-info': dt.selected, active: isActive(dt)}\" ng-click=\"select(dt.date)\" ng-disabled=\"dt.disabled\" tabindex=\"-1\"><span ng-class=\"{'text-info': dt.current}\">{{dt.label}}</span></button>\n" +
3943
+ " </td>\n" +
3944
+ " </tr>\n" +
3945
+ " </tbody>\n" +
3946
+ "</table>\n" +
3947
+ "");
3948
+ }]);
3949
+
3524
3950
  angular.module("template/modal/backdrop.html", []).run(["$templateCache", function($templateCache) {
3525
3951
  $templateCache.put("template/modal/backdrop.html",
3526
- "<div class=\"modal-backdrop fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1040 + index*10}\"></div>");
3952
+ "<div class=\"modal-backdrop fade\"\n" +
3953
+ " ng-class=\"{in: animate}\"\n" +
3954
+ " ng-style=\"{'z-index': 1040 + (index && 1 || 0) + index*10}\"\n" +
3955
+ "></div>\n" +
3956
+ "");
3527
3957
  }]);
3528
3958
 
3529
3959
  angular.module("template/modal/window.html", []).run(["$templateCache", function($templateCache) {
3530
3960
  $templateCache.put("template/modal/window.html",
3531
- "<div tabindex=\"-1\" class=\"modal fade {{ windowClass }}\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
3532
- " <div class=\"modal-dialog\"><div class=\"modal-content\" ng-transclude></div></div>\n" +
3961
+ "<div tabindex=\"-1\" role=\"dialog\" class=\"modal fade\" ng-class=\"{in: animate}\" ng-style=\"{'z-index': 1050 + index*10, display: 'block'}\" ng-click=\"close($event)\">\n" +
3962
+ " <div class=\"modal-dialog\" ng-class=\"{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}\"><div class=\"modal-content\" ng-transclude></div></div>\n" +
3533
3963
  "</div>");
3534
3964
  }]);
3535
3965
 
3536
3966
  angular.module("template/pagination/pager.html", []).run(["$templateCache", function($templateCache) {
3537
3967
  $templateCache.put("template/pagination/pager.html",
3538
3968
  "<ul class=\"pager\">\n" +
3539
- " <li ng-repeat=\"page in pages\" ng-class=\"{disabled: page.disabled, previous: page.previous, next: page.next}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3969
+ " <li ng-class=\"{disabled: noPrevious(), previous: align}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" +
3970
+ " <li ng-class=\"{disabled: noNext(), next: align}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" +
3540
3971
  "</ul>");
3541
3972
  }]);
3542
3973
 
3543
3974
  angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache) {
3544
3975
  $templateCache.put("template/pagination/pagination.html",
3545
3976
  "<ul class=\"pagination\">\n" +
3546
- " <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3977
+ " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(1)\">{{getText('first')}}</a></li>\n" +
3978
+ " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noPrevious()}\"><a href ng-click=\"selectPage(page - 1)\">{{getText('previous')}}</a></li>\n" +
3979
+ " <li ng-repeat=\"page in pages track by $index\" ng-class=\"{active: page.active}\"><a href ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>\n" +
3980
+ " <li ng-if=\"directionLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(page + 1)\">{{getText('next')}}</a></li>\n" +
3981
+ " <li ng-if=\"boundaryLinks\" ng-class=\"{disabled: noNext()}\"><a href ng-click=\"selectPage(totalPages)\">{{getText('last')}}</a></li>\n" +
3547
3982
  "</ul>");
3548
3983
  }]);
3549
3984
 
@@ -3580,7 +4015,7 @@ angular.module("template/popover/popover.html", []).run(["$templateCache", funct
3580
4015
 
3581
4016
  angular.module("template/progressbar/bar.html", []).run(["$templateCache", function($templateCache) {
3582
4017
  $templateCache.put("template/progressbar/bar.html",
3583
- "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div>");
4018
+ "<div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>");
3584
4019
  }]);
3585
4020
 
3586
4021
  angular.module("template/progressbar/progress.html", []).run(["$templateCache", function($templateCache) {
@@ -3590,13 +4025,17 @@ angular.module("template/progressbar/progress.html", []).run(["$templateCache",
3590
4025
 
3591
4026
  angular.module("template/progressbar/progressbar.html", []).run(["$templateCache", function($templateCache) {
3592
4027
  $templateCache.put("template/progressbar/progressbar.html",
3593
- "<div class=\"progress\"><div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" ng-transclude></div></div>");
4028
+ "<div class=\"progress\">\n" +
4029
+ " <div class=\"progress-bar\" ng-class=\"type && 'progress-bar-' + type\" role=\"progressbar\" aria-valuenow=\"{{value}}\" aria-valuemin=\"0\" aria-valuemax=\"{{max}}\" ng-style=\"{width: percent + '%'}\" aria-valuetext=\"{{percent | number:0}}%\" ng-transclude></div>\n" +
4030
+ "</div>");
3594
4031
  }]);
3595
4032
 
3596
4033
  angular.module("template/rating/rating.html", []).run(["$templateCache", function($templateCache) {
3597
4034
  $templateCache.put("template/rating/rating.html",
3598
- "<span ng-mouseleave=\"reset()\">\n" +
3599
- " <i ng-repeat=\"r in range\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < val && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\"></i>\n" +
4035
+ "<span ng-mouseleave=\"reset()\" ng-keydown=\"onKeydown($event)\" tabindex=\"0\" role=\"slider\" aria-valuemin=\"0\" aria-valuemax=\"{{range.length}}\" aria-valuenow=\"{{value}}\">\n" +
4036
+ " <i ng-repeat=\"r in range track by $index\" ng-mouseenter=\"enter($index + 1)\" ng-click=\"rate($index + 1)\" class=\"glyphicon\" ng-class=\"$index < value && (r.stateOn || 'glyphicon-star') || (r.stateOff || 'glyphicon-star-empty')\">\n" +
4037
+ " <span class=\"sr-only\">({{ $index < value ? '*' : ' ' }})</span>\n" +
4038
+ " </i>\n" +
3600
4039
  "</span>");
3601
4040
  }]);
3602
4041
 
@@ -3618,8 +4057,8 @@ angular.module("template/tabs/tabset-titles.html", []).run(["$templateCache", fu
3618
4057
  angular.module("template/tabs/tabset.html", []).run(["$templateCache", function($templateCache) {
3619
4058
  $templateCache.put("template/tabs/tabset.html",
3620
4059
  "\n" +
3621
- "<div class=\"tabbable\">\n" +
3622
- " <ul class=\"nav {{type && 'nav-' + type}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
4060
+ "<div>\n" +
4061
+ " <ul class=\"nav nav-{{type || 'tabs'}}\" ng-class=\"{'nav-stacked': vertical, 'nav-justified': justified}\" ng-transclude></ul>\n" +
3623
4062
  " <div class=\"tab-content\">\n" +
3624
4063
  " <div class=\"tab-pane\" \n" +
3625
4064
  " ng-repeat=\"tab in tabs\" \n" +
@@ -3669,8 +4108,8 @@ angular.module("template/typeahead/typeahead-match.html", []).run(["$templateCac
3669
4108
 
3670
4109
  angular.module("template/typeahead/typeahead-popup.html", []).run(["$templateCache", function($templateCache) {
3671
4110
  $templateCache.put("template/typeahead/typeahead-popup.html",
3672
- "<ul class=\"dropdown-menu\" ng-style=\"{display: isOpen()&&'block' || 'none', top: position.top+'px', left: position.left+'px'}\">\n" +
3673
- " <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\">\n" +
4111
+ "<ul class=\"dropdown-menu\" ng-if=\"isOpen()\" ng-style=\"{top: position.top+'px', left: position.left+'px'}\" style=\"display: block;\" role=\"listbox\" aria-hidden=\"{{!isOpen()}}\">\n" +
4112
+ " <li ng-repeat=\"match in matches track by $index\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\" ng-click=\"selectMatch($index)\" role=\"option\" id=\"{{match.id}}\">\n" +
3674
4113
  " <div typeahead-match index=\"$index\" match=\"match\" query=\"query\" template-url=\"templateUrl\"></div>\n" +
3675
4114
  " </li>\n" +
3676
4115
  "</ul>");