angular-ui-bootstrap-rails 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 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>");