angular-ui-bootstrap-rails 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
1
  module AngularUI
2
2
  module Bootstrap
3
3
  module Rails
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
7
7
  end
@@ -1,6 +1,6 @@
1
- angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.carousel","ui.bootstrap.collapse","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.popover","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.transition"]);
1
+ angular.module("ui.bootstrap", ["ui.bootstrap.tpls", "ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.collapse","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.popover","ui.bootstrap.tabs","ui.bootstrap.tooltip","ui.bootstrap.transition","ui.bootstrap.typeahead"]);
2
2
 
3
- 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/dialog/message.html","template/pagination/pagination.html","template/popover/popover.html","template/tabs/pane.html","template/tabs/tabs.html","template/tooltip/tooltip-popup.html"]);
3
+ 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/dialog/message.html","template/pagination/pagination.html","template/popover/popover.html","template/tabs/pane.html","template/tabs/tabs.html","template/tooltip/tooltip-popup.html","template/typeahead/typeahead.html"]);
4
4
 
5
5
  angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
6
6
 
@@ -43,11 +43,11 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
43
43
  }
44
44
  };
45
45
 
46
- }]);
46
+ }])
47
47
 
48
48
  // The accordion directive simply sets up the directive controller
49
49
  // and adds an accordion CSS class to itself element.
50
- angular.module('ui.bootstrap.accordion').directive('accordion', function () {
50
+ .directive('accordion', function () {
51
51
  return {
52
52
  restrict:'EA',
53
53
  controller:'AccordionController',
@@ -55,10 +55,10 @@ angular.module('ui.bootstrap.accordion').directive('accordion', function () {
55
55
  replace: false,
56
56
  templateUrl: 'template/accordion/accordion.html'
57
57
  };
58
- });
58
+ })
59
59
 
60
60
  // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
61
- angular.module('ui.bootstrap.accordion').directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
61
+ .directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
62
62
  return {
63
63
  require:'^accordion', // We need this directive to be inside an accordion
64
64
  restrict:'EA',
@@ -66,6 +66,11 @@ angular.module('ui.bootstrap.accordion').directive('accordionGroup', ['$parse',
66
66
  replace: true, // The element containing the directive will be replaced with the template
67
67
  templateUrl:'template/accordion/accordion-group.html',
68
68
  scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
69
+ controller: ['$scope', function($scope) {
70
+ this.setHeading = function(element) {
71
+ this.heading = element;
72
+ };
73
+ }],
69
74
  link: function(scope, element, attrs, accordionCtrl) {
70
75
  var getIsOpen, setIsOpen;
71
76
 
@@ -93,10 +98,51 @@ angular.module('ui.bootstrap.accordion').directive('accordionGroup', ['$parse',
93
98
  setIsOpen(scope.$parent, value);
94
99
  }
95
100
  });
101
+ }
102
+ };
103
+ }])
96
104
 
105
+ // Use accordion-heading below an accordion-group to provide a heading containing HTML
106
+ // <accordion-group>
107
+ // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
108
+ // </accordion-group>
109
+ .directive('accordionHeading', function() {
110
+ return {
111
+ restrict: 'E',
112
+ transclude: true, // Grab the contents to be used as the heading
113
+ template: '', // In effect remove this element!
114
+ replace: true,
115
+ require: '^accordionGroup',
116
+ compile: function(element, attr, transclude) {
117
+ return function link(scope, element, attr, accordionGroupCtrl) {
118
+ // Pass the heading to the accordion-group controller
119
+ // so that it can be transcluded into the right place in the template
120
+ // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
121
+ accordionGroupCtrl.setHeading(transclude(scope, function() {}));
122
+ };
97
123
  }
98
124
  };
99
- }]);
125
+ })
126
+
127
+ // Use in the accordion-group template to indicate where you want the heading to be transcluded
128
+ // You must provide the property on the accordion-group controller that will hold the transcluded element
129
+ // <div class="accordion-group">
130
+ // <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
131
+ // ...
132
+ // </div>
133
+ .directive('accordionTransclude', function() {
134
+ return {
135
+ require: '^accordionGroup',
136
+ link: function(scope, element, attr, controller) {
137
+ scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
138
+ if ( heading ) {
139
+ element.html('');
140
+ element.append(heading);
141
+ }
142
+ });
143
+ }
144
+ };
145
+ });
100
146
 
101
147
  angular.module("ui.bootstrap.alert", []).directive('alert', function () {
102
148
  return {
@@ -110,11 +156,87 @@ angular.module("ui.bootstrap.alert", []).directive('alert', function () {
110
156
  }
111
157
  };
112
158
  });
159
+ angular.module('ui.bootstrap.buttons', [])
160
+
161
+ .constant('buttonConfig', {
162
+ activeClass:'active',
163
+ toggleEvent:'click'
164
+ })
165
+
166
+ .directive('btnRadio', ['buttonConfig', function (buttonConfig) {
167
+ var activeClass = buttonConfig.activeClass || 'active';
168
+ var toggleEvent = buttonConfig.toggleEvent || 'click';
169
+
170
+ return {
171
+
172
+ require:'ngModel',
173
+ link:function (scope, element, attrs, ngModelCtrl) {
174
+
175
+ var value = scope.$eval(attrs.btnRadio);
176
+
177
+ //model -> UI
178
+ scope.$watch(function () {
179
+ return ngModelCtrl.$modelValue;
180
+ }, function (modelValue) {
181
+ if (angular.equals(modelValue, value)){
182
+ element.addClass(activeClass);
183
+ } else {
184
+ element.removeClass(activeClass);
185
+ }
186
+ });
187
+
188
+ //ui->model
189
+ element.bind(toggleEvent, function () {
190
+ if (!element.hasClass(activeClass)) {
191
+ scope.$apply(function () {
192
+ ngModelCtrl.$setViewValue(value);
193
+ });
194
+ }
195
+ });
196
+ }
197
+ };
198
+ }])
199
+
200
+ .directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
201
+
202
+ var activeClass = buttonConfig.activeClass || 'active';
203
+ var toggleEvent = buttonConfig.toggleEvent || 'click';
204
+
205
+ return {
206
+ require:'ngModel',
207
+ link:function (scope, element, attrs, ngModelCtrl) {
208
+
209
+ var trueValue = scope.$eval(attrs.btnCheckboxTrue);
210
+ var falseValue = scope.$eval(attrs.btnCheckboxFalse);
211
+
212
+ trueValue = angular.isDefined(trueValue) ? trueValue : true;
213
+ falseValue = angular.isDefined(falseValue) ? falseValue : false;
214
+
215
+ //model -> UI
216
+ scope.$watch(function () {
217
+ return ngModelCtrl.$modelValue;
218
+ }, function (modelValue) {
219
+ if (angular.equals(modelValue, trueValue)) {
220
+ element.addClass(activeClass);
221
+ } else {
222
+ element.removeClass(activeClass);
223
+ }
224
+ });
225
+
226
+ //ui->model
227
+ element.bind(toggleEvent, function () {
228
+ scope.$apply(function () {
229
+ ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
230
+ });
231
+ });
232
+ }
233
+ };
234
+ }]);
113
235
  /*
114
236
  *
115
- * Angular Bootstrap Carousel
237
+ * AngularJS Bootstrap Carousel
116
238
  *
117
- * The carousel has all of the function that the original Bootstrap carousel has, except for animations.
239
+ * A pure AngularJS carousel.
118
240
  *
119
241
  * For no interval set the interval to non-number, or milliseconds of desired interval
120
242
  * Template: <carousel interval="none"><slide>{{anything}}</slide></carousel>
@@ -197,6 +319,18 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
197
319
  return self.select(slides[newIndex], 'prev');
198
320
  };
199
321
 
322
+ $scope.select = function(slide) {
323
+ self.select(slide);
324
+ };
325
+
326
+ $scope.isActive = function(slide) {
327
+ return self.currentSlide === slide;
328
+ };
329
+
330
+ $scope.slides = function() {
331
+ return slides;
332
+ };
333
+
200
334
  $scope.$watch('interval', restartTimer);
201
335
  function restartTimer() {
202
336
  if (currentTimeout) {
@@ -327,7 +461,11 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
327
461
  //When we have a change of scrollHeight we are setting again the correct height if the group is opened
328
462
  if (element[0].scrollHeight !== 0) {
329
463
  if (!isCollapsed) {
330
- fixUpHeight(scope, element, element[0].scrollHeight + 'px');
464
+ if (initialAnimSkip) {
465
+ fixUpHeight(scope, element, element[0].scrollHeight + 'px');
466
+ } else {
467
+ fixUpHeight(scope, element, 'auto');
468
+ }
331
469
  }
332
470
  }
333
471
  });
@@ -403,22 +541,25 @@ dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', fu
403
541
  dialogModule.provider("$dialog", function(){
404
542
 
405
543
  // The default options for all dialogs.
406
- var defaults = {
407
- backdrop: true,
408
- modalClass: 'modal',
409
- backdropClass: 'modal-backdrop',
544
+ var defaults = {
545
+ backdrop: true,
546
+ dialogClass: 'modal',
547
+ backdropClass: 'modal-backdrop',
410
548
  transitionClass: 'fade',
411
549
  triggerClass: 'in',
412
- resolve:{},
413
- backdropFade: false,
414
- modalFade:false,
415
- keyboard: true, // close with esc key
416
- backdropClick: true // only in conjunction with backdrop=true
550
+ dialogOpenClass: 'modal-open',
551
+ resolve:{},
552
+ backdropFade: false,
553
+ dialogFade:false,
554
+ keyboard: true, // close with esc key
555
+ backdropClick: true // only in conjunction with backdrop=true
417
556
  /* other options: template, templateUrl, controller */
418
557
  };
419
558
 
420
559
  var globalOptions = {};
421
560
 
561
+ var activeBackdrops = {value : 0};
562
+
422
563
  // The `options({})` allows global configuration of all dialogs in the application.
423
564
  //
424
565
  // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
@@ -430,8 +571,8 @@ dialogModule.provider("$dialog", function(){
430
571
  };
431
572
 
432
573
  // Returns the actual `$dialog` service that is injected in controllers
433
- this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition",
434
- function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition) {
574
+ this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
575
+ function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
435
576
 
436
577
  var body = $document.find('body');
437
578
 
@@ -443,9 +584,9 @@ dialogModule.provider("$dialog", function(){
443
584
 
444
585
  // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
445
586
  // containing at lest template or templateUrl and controller:
446
- //
587
+ //
447
588
  // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
448
- //
589
+ //
449
590
  // Dialogs can also be created using templateUrl and controller as distinct arguments:
450
591
  //
451
592
  // var d = new Dialog('path/to/dialog.html', MyDialogController);
@@ -459,8 +600,8 @@ dialogModule.provider("$dialog", function(){
459
600
  this.backdropEl.removeClass(options.triggerClass);
460
601
  }
461
602
 
462
- this.modalEl = createElement(options.modalClass);
463
- if(options.modalFade){
603
+ this.modalEl = createElement(options.dialogClass);
604
+ if(options.dialogFade){
464
605
  this.modalEl.addClass(options.transitionClass);
465
606
  this.modalEl.removeClass(options.triggerClass);
466
607
  }
@@ -496,13 +637,13 @@ dialogModule.provider("$dialog", function(){
496
637
  if(controller){
497
638
  options.controller = controller;
498
639
  }
499
-
640
+
500
641
  if(!(options.template || options.templateUrl)) {
501
642
  throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
502
643
  }
503
644
 
504
645
  this._loadResolves().then(function(locals) {
505
- var $scope = locals.$scope = self.$scope = $rootScope.$new();
646
+ var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();
506
647
 
507
648
  self.modalEl.html(locals.$template);
508
649
 
@@ -511,12 +652,13 @@ dialogModule.provider("$dialog", function(){
511
652
  self.modalEl.contents().data('ngControllerController', ctrl);
512
653
  }
513
654
 
514
- $compile(self.modalEl.contents())($scope);
655
+ $compile(self.modalEl)($scope);
515
656
  self._addElementsToDom();
657
+ body.addClass(self.options.dialogOpenClass);
516
658
 
517
659
  // trigger tranisitions
518
660
  setTimeout(function(){
519
- if(self.options.modalFade){ self.modalEl.addClass(self.options.triggerClass); }
661
+ if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
520
662
  if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
521
663
  });
522
664
 
@@ -532,6 +674,7 @@ dialogModule.provider("$dialog", function(){
532
674
  var self = this;
533
675
  var fadingElements = this._getFadingElements();
534
676
 
677
+ body.removeClass(self.options.dialogOpenClass);
535
678
  if(fadingElements.length > 0){
536
679
  for (var i = fadingElements.length - 1; i >= 0; i--) {
537
680
  $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
@@ -554,7 +697,7 @@ dialogModule.provider("$dialog", function(){
554
697
 
555
698
  Dialog.prototype._getFadingElements = function(){
556
699
  var elements = [];
557
- if(this.options.modalFade){
700
+ if(this.options.dialogFade){
558
701
  elements.push(this.modalEl);
559
702
  }
560
703
  if(this.options.backdropFade){
@@ -583,13 +726,26 @@ dialogModule.provider("$dialog", function(){
583
726
 
584
727
  Dialog.prototype._addElementsToDom = function(){
585
728
  body.append(this.modalEl);
586
- if(this.options.backdrop) { body.append(this.backdropEl); }
729
+
730
+ if(this.options.backdrop) {
731
+ if (activeBackdrops.value === 0) {
732
+ body.append(this.backdropEl);
733
+ }
734
+ activeBackdrops.value++;
735
+ }
736
+
587
737
  this._open = true;
588
738
  };
589
739
 
590
740
  Dialog.prototype._removeElementsFromDom = function(){
591
741
  this.modalEl.remove();
592
- if(this.options.backdrop) { this.backdropEl.remove(); }
742
+
743
+ if(this.options.backdrop) {
744
+ activeBackdrops.value--;
745
+ if (activeBackdrops.value === 0) {
746
+ this.backdropEl.remove();
747
+ }
748
+ }
593
749
  this._open = false;
594
750
  };
595
751
 
@@ -606,7 +762,7 @@ dialogModule.provider("$dialog", function(){
606
762
 
607
763
  angular.forEach(this.options.resolve || [], function(value, key) {
608
764
  keys.push(key);
609
- values.push(value);
765
+ values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
610
766
  });
611
767
 
612
768
  keys.push('$template');
@@ -638,11 +794,15 @@ dialogModule.provider("$dialog", function(){
638
794
  // * `label`: the label of the button
639
795
  // * `cssClass`: additional css class(es) to apply to the button for styling
640
796
  messageBox: function(title, message, buttons){
641
- return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve: {model: {
642
- title: title,
643
- message: message,
644
- buttons: buttons
645
- }}});
797
+ return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
798
+ {model: function() {
799
+ return {
800
+ title: title,
801
+ message: message,
802
+ buttons: buttons
803
+ };
804
+ }
805
+ }});
646
806
  }
647
807
  };
648
808
  }];
@@ -709,100 +869,97 @@ angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle',
709
869
  };
710
870
  }]);
711
871
 
712
- angular.module('ui.bootstrap.modal', []).directive('modal', ['$parse',function($parse) {
872
+ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
873
+ .directive('modal', ['$parse', '$dialog', function($parse, $dialog) {
713
874
  var backdropEl;
714
875
  var body = angular.element(document.getElementsByTagName('body')[0]);
715
- var defaultOpts = {
716
- backdrop: true,
717
- escape: true
718
- };
719
876
  return {
720
877
  restrict: 'EA',
878
+ terminal: true,
721
879
  link: function(scope, elm, attrs) {
722
- var opts = angular.extend(defaultOpts, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
880
+ var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
723
881
  var shownExpr = attrs.modal || attrs.show;
724
882
  var setClosed;
725
883
 
884
+ // Create a dialog with the template as the contents of the directive
885
+ // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
886
+ opts = angular.extend(opts, {
887
+ template: elm.html(),
888
+ resolve: { $scope: function() { return scope; } }
889
+ });
890
+ var dialog = $dialog.dialog(opts);
891
+
892
+ elm.remove();
893
+
726
894
  if (attrs.close) {
727
895
  setClosed = function() {
728
- scope.$apply(attrs.close);
896
+ $parse(attrs.close)(scope);
729
897
  };
730
898
  } else {
731
- setClosed = function() {
732
- scope.$apply(function() {
733
- $parse(shownExpr).assign(scope, false);
734
- });
735
- };
736
- }
737
- elm.addClass('modal');
738
-
739
- if (opts.backdrop && !backdropEl) {
740
- backdropEl = angular.element('<div class="modal-backdrop"></div>');
741
- backdropEl.css('display','none');
742
- body.append(backdropEl);
743
- }
744
-
745
- function setShown(shown) {
746
- scope.$apply(function() {
747
- model.assign(scope, shown);
748
- });
749
- }
750
-
751
- function escapeClose(evt) {
752
- if (evt.which === 27) { setClosed(); }
753
- }
754
- function clickClose() {
755
- setClosed();
756
- }
757
-
758
- function close() {
759
- if (opts.escape) { body.unbind('keyup', escapeClose); }
760
- if (opts.backdrop) {
761
- backdropEl.css('display', 'none').removeClass('in');
762
- backdropEl.unbind('click', clickClose);
763
- }
764
- elm.css('display', 'none').removeClass('in');
765
- body.removeClass('modal-open');
766
- }
767
- function open() {
768
- if (opts.escape) { body.bind('keyup', escapeClose); }
769
- if (opts.backdrop) {
770
- backdropEl.css('display', 'block').addClass('in');
771
- if(opts.backdrop != "static") {
772
- backdropEl.bind('click', clickClose);
899
+ setClosed = function() {
900
+ if (angular.isFunction($parse(shownExpr).assign)) {
901
+ $parse(shownExpr).assign(scope, false);
773
902
  }
774
- }
775
- elm.css('display', 'block').addClass('in');
776
- body.addClass('modal-open');
903
+ };
777
904
  }
778
905
 
779
906
  scope.$watch(shownExpr, function(isShown, oldShown) {
780
907
  if (isShown) {
781
- open();
908
+ dialog.open().then(function(){
909
+ setClosed();
910
+ });
782
911
  } else {
783
- close();
912
+ //Make sure it is not opened
913
+ if (dialog.isOpen()){
914
+ dialog.close();
915
+ }
784
916
  }
785
917
  });
786
918
  }
787
919
  };
788
- }]);
789
-
920
+ }]);
790
921
  angular.module('ui.bootstrap.pagination', [])
791
922
 
792
- .directive('pagination', function() {
923
+ .constant('paginationConfig', {
924
+ boundaryLinks: false,
925
+ directionLinks: true,
926
+ firstText: 'First',
927
+ previousText: 'Previous',
928
+ nextText: 'Next',
929
+ lastText: 'Last'
930
+ })
931
+
932
+ .directive('pagination', ['paginationConfig', function(paginationConfig) {
793
933
  return {
794
934
  restrict: 'EA',
795
935
  scope: {
796
936
  numPages: '=',
797
937
  currentPage: '=',
798
938
  maxSize: '=',
799
- onSelectPage: '&',
800
- nextText: '@',
801
- previousText: '@'
939
+ onSelectPage: '&'
802
940
  },
803
941
  templateUrl: 'template/pagination/pagination.html',
804
942
  replace: true,
805
- link: function(scope) {
943
+ link: function(scope, element, attrs) {
944
+
945
+ // Setup configuration parameters
946
+ var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
947
+ var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
948
+ var firstText = angular.isDefined(attrs.firstText) ? attrs.firstText : paginationConfig.firstText;
949
+ var previousText = angular.isDefined(attrs.previousText) ? attrs.previousText : paginationConfig.previousText;
950
+ var nextText = angular.isDefined(attrs.nextText) ? attrs.nextText : paginationConfig.nextText;
951
+ var lastText = angular.isDefined(attrs.lastText) ? attrs.lastText : paginationConfig.lastText;
952
+
953
+ // Create page object used in template
954
+ function makePage(number, text, isActive, isDisabled) {
955
+ return {
956
+ number: number,
957
+ text: text,
958
+ active: isActive,
959
+ disabled: isDisabled
960
+ };
961
+ }
962
+
806
963
  scope.$watch('numPages + currentPage + maxSize', function() {
807
964
  scope.pages = [];
808
965
 
@@ -818,9 +975,31 @@ angular.module('ui.bootstrap.pagination', [])
818
975
  startPage = startPage - ((startPage + maxSize - 1) - scope.numPages );
819
976
  }
820
977
 
821
- for(var i=0; i < maxSize && i < scope.numPages ;i++) {
822
- scope.pages.push(startPage + i);
978
+ // Add page number links
979
+ for (var number = startPage, max = startPage + maxSize; number < max; number++) {
980
+ var page = makePage(number, number, scope.isActive(number), false);
981
+ scope.pages.push(page);
982
+ }
983
+
984
+ // Add previous & next links
985
+ if (directionLinks) {
986
+ var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
987
+ scope.pages.unshift(previousPage);
988
+
989
+ var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
990
+ scope.pages.push(nextPage);
991
+ }
992
+
993
+ // Add first & last links
994
+ if (boundaryLinks) {
995
+ var firstPage = makePage(1, firstText, false, scope.noPrevious());
996
+ scope.pages.unshift(firstPage);
997
+
998
+ var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
999
+ scope.pages.push(lastPage);
823
1000
  }
1001
+
1002
+
824
1003
  if ( scope.currentPage > scope.numPages ) {
825
1004
  scope.selectPage(scope.numPages);
826
1005
  }
@@ -836,25 +1015,14 @@ angular.module('ui.bootstrap.pagination', [])
836
1015
  };
837
1016
 
838
1017
  scope.selectPage = function(page) {
839
- if ( ! scope.isActive(page) ) {
1018
+ if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) {
840
1019
  scope.currentPage = page;
841
1020
  scope.onSelectPage({ page: page });
842
1021
  }
843
1022
  };
844
-
845
- scope.selectPrevious = function() {
846
- if ( !scope.noPrevious() ) {
847
- scope.selectPage(scope.currentPage-1);
848
- }
849
- };
850
- scope.selectNext = function() {
851
- if ( !scope.noNext() ) {
852
- scope.selectPage(scope.currentPage+1);
853
- }
854
- };
855
1023
  }
856
1024
  };
857
- });
1025
+ }]);
858
1026
  /**
859
1027
  * The following features are still outstanding: popup delay, animation as a
860
1028
  * function, placement as a function, inside, support for more triggers than
@@ -869,7 +1037,7 @@ angular.module( 'ui.bootstrap.popover', [] )
869
1037
  templateUrl: 'template/popover/popover.html'
870
1038
  };
871
1039
  })
872
- .directive( 'popover', [ '$compile', '$timeout', '$parse', function ( $compile, $timeout, $parse ) {
1040
+ .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window ) {
873
1041
 
874
1042
  var template =
875
1043
  '<popover-popup '+
@@ -909,15 +1077,15 @@ angular.module( 'ui.bootstrap.popover', [] )
909
1077
 
910
1078
  // Calculate the current position and size of the directive element.
911
1079
  function getPosition() {
1080
+ var boundingClientRect = element[0].getBoundingClientRect();
912
1081
  return {
913
1082
  width: element.prop( 'offsetWidth' ),
914
1083
  height: element.prop( 'offsetHeight' ),
915
- top: element.prop( 'offsetTop' ),
916
- left: element.prop( 'offsetLeft' )
1084
+ top: boundingClientRect.top + $window.pageYOffset,
1085
+ left: boundingClientRect.left + $window.pageXOffset
917
1086
  };
918
1087
  }
919
-
920
- // Show the popover popup element.
1088
+
921
1089
  function show() {
922
1090
  var position,
923
1091
  ttWidth,
@@ -1100,7 +1268,7 @@ angular.module( 'ui.bootstrap.tooltip', [] )
1100
1268
  templateUrl: 'template/tooltip/tooltip-popup.html'
1101
1269
  };
1102
1270
  })
1103
- .directive( 'tooltip', [ '$compile', '$timeout', '$parse', function ( $compile, $timeout, $parse ) {
1271
+ .directive( 'tooltip', [ '$compile', '$timeout', '$parse', '$window', function ( $compile, $timeout, $parse, $window) {
1104
1272
 
1105
1273
  var template =
1106
1274
  '<tooltip-popup '+
@@ -1135,11 +1303,12 @@ angular.module( 'ui.bootstrap.tooltip', [] )
1135
1303
 
1136
1304
  // Calculate the current position and size of the directive element.
1137
1305
  function getPosition() {
1306
+ var boundingClientRect = element[0].getBoundingClientRect();
1138
1307
  return {
1139
1308
  width: element.prop( 'offsetWidth' ),
1140
1309
  height: element.prop( 'offsetHeight' ),
1141
- top: element.prop( 'offsetTop' ),
1142
- left: element.prop( 'offsetLeft' )
1310
+ top: boundingClientRect.top + $window.pageYOffset,
1311
+ left: boundingClientRect.left + $window.pageXOffset
1143
1312
  };
1144
1313
  }
1145
1314
 
@@ -1149,7 +1318,12 @@ angular.module( 'ui.bootstrap.tooltip', [] )
1149
1318
  ttWidth,
1150
1319
  ttHeight,
1151
1320
  ttPosition;
1152
-
1321
+
1322
+ //don't show empty tooltips
1323
+ if (!scope.tt_tooltip) {
1324
+ return;
1325
+ }
1326
+
1153
1327
  // If there is a pending remove transition, we must cancel it, lest the
1154
1328
  // toolip be mysteriously removed.
1155
1329
  if ( transitionTimeout ) {
@@ -1165,7 +1339,7 @@ angular.module( 'ui.bootstrap.tooltip', [] )
1165
1339
 
1166
1340
  // Get the position of the directive element.
1167
1341
  position = getPosition();
1168
-
1342
+
1169
1343
  // Get the height and width of the tooltip so we can center it.
1170
1344
  ttWidth = tooltip.prop( 'offsetWidth' );
1171
1345
  ttHeight = tooltip.prop( 'offsetHeight' );
@@ -1319,10 +1493,212 @@ angular.module('ui.bootstrap.transition', [])
1319
1493
  return $transition;
1320
1494
  }]);
1321
1495
 
1496
+ angular.module('ui.bootstrap.typeahead', [])
1497
+
1498
+ /**
1499
+ * A helper service that can parse typeahead's syntax (string provided by users)
1500
+ * Extracted to a separate service for ease of unit testing
1501
+ */
1502
+ .factory('typeaheadParser', ['$parse', function ($parse) {
1503
+
1504
+ // 00000111000000000000022200000000000000003333333333333330000000000044000
1505
+ var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
1506
+
1507
+ return {
1508
+ parse:function (input) {
1509
+
1510
+ var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
1511
+ if (!match) {
1512
+ throw new Error(
1513
+ "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
1514
+ " but got '" + input + "'.");
1515
+ }
1516
+
1517
+ return {
1518
+ itemName:match[3],
1519
+ source:$parse(match[4]),
1520
+ viewMapper:$parse(match[2] || match[1]),
1521
+ modelMapper:$parse(match[1])
1522
+ };
1523
+ }
1524
+ };
1525
+ }])
1526
+
1527
+ //options - min length
1528
+ .directive('typeahead', ['$compile', '$q', 'typeaheadParser', function ($compile, $q, typeaheadParser) {
1529
+
1530
+ var HOT_KEYS = [9, 13, 27, 38, 40];
1531
+
1532
+ return {
1533
+ require:'ngModel',
1534
+ link:function (originalScope, element, attrs, modelCtrl) {
1535
+
1536
+ var selected = modelCtrl.$modelValue;
1537
+
1538
+ //minimal no of characters that needs to be entered before typeahead kicks-in
1539
+ var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
1540
+
1541
+ //expressions used by typeahead
1542
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
1543
+
1544
+ //create a child scope for the typeahead directive so we are not polluting original scope
1545
+ //with typeahead-specific data (matches, query etc.)
1546
+ var scope = originalScope.$new();
1547
+ originalScope.$on('$destroy', function(){
1548
+ scope.$destroy();
1549
+ });
1550
+
1551
+ var resetMatches = function() {
1552
+ scope.matches = [];
1553
+ scope.activeIdx = -1;
1554
+ };
1555
+
1556
+ var getMatchesAsync = function(inputValue) {
1557
+
1558
+ var locals = {$viewValue: inputValue};
1559
+ $q.when(parserResult.source(scope, locals)).then(function(matches) {
1560
+
1561
+ //it might happen that several async queries were in progress if a user were typing fast
1562
+ //but we are interested only in responses that correspond to the current view value
1563
+ if (inputValue === modelCtrl.$viewValue) {
1564
+ if (matches.length > 0) {
1565
+
1566
+ scope.activeIdx = 0;
1567
+ scope.matches.length = 0;
1568
+
1569
+ //transform labels
1570
+ for(var i=0; i<matches.length; i++) {
1571
+ locals[parserResult.itemName] = matches[i];
1572
+ scope.matches.push({
1573
+ label: parserResult.viewMapper(scope, locals),
1574
+ model: matches[i]
1575
+ });
1576
+ }
1577
+
1578
+ scope.query = inputValue;
1579
+
1580
+ } else {
1581
+ resetMatches();
1582
+ }
1583
+ }
1584
+ }, resetMatches);
1585
+ };
1586
+
1587
+ resetMatches();
1588
+
1589
+ //we need to propagate user's query so we can higlight matches
1590
+ scope.query = undefined;
1591
+
1592
+ //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
1593
+ //$parsers kick-in on all the changes coming from the vview as well as manually triggered by $setViewValue
1594
+ modelCtrl.$parsers.push(function (inputValue) {
1595
+
1596
+ resetMatches();
1597
+ if (selected) {
1598
+ return inputValue;
1599
+ } else {
1600
+ if (inputValue && inputValue.length >= minSearch) {
1601
+ getMatchesAsync(inputValue);
1602
+ }
1603
+ }
1604
+
1605
+ return undefined;
1606
+ });
1607
+
1608
+ modelCtrl.$render = function () {
1609
+ var locals = {};
1610
+ locals[parserResult.itemName] = selected;
1611
+ element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
1612
+ selected = undefined;
1613
+ };
1614
+
1615
+ scope.select = function (activeIdx) {
1616
+ //called from within the $digest() cycle
1617
+ var locals = {};
1618
+ locals[parserResult.itemName] = selected = scope.matches[activeIdx].model;
1619
+
1620
+ modelCtrl.$setViewValue(parserResult.modelMapper(scope, locals));
1621
+ modelCtrl.$render();
1622
+ };
1623
+
1624
+ //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(9)
1625
+ element.bind('keydown', function (evt) {
1626
+
1627
+ //typeahead is open and an "interesting" key was pressed
1628
+ if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
1629
+ return;
1630
+ }
1631
+
1632
+ evt.preventDefault();
1633
+
1634
+ if (evt.which === 40) {
1635
+ scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
1636
+ scope.$digest();
1637
+
1638
+ } else if (evt.which === 38) {
1639
+ scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
1640
+ scope.$digest();
1641
+
1642
+ } else if (evt.which === 13 || evt.which === 9) {
1643
+ scope.$apply(function () {
1644
+ scope.select(scope.activeIdx);
1645
+ });
1646
+
1647
+ } else if (evt.which === 27) {
1648
+ scope.matches = [];
1649
+ scope.$digest();
1650
+ }
1651
+ });
1652
+
1653
+ var tplElCompiled = $compile("<typeahead-popup matches='matches' active='activeIdx' select='select(activeIdx)' "+
1654
+ "query='query'></typeahead-popup>")(scope);
1655
+ element.after(tplElCompiled);
1656
+ }
1657
+ };
1658
+
1659
+ }])
1660
+
1661
+ .directive('typeaheadPopup', function () {
1662
+ return {
1663
+ restrict:'E',
1664
+ scope:{
1665
+ matches:'=',
1666
+ query:'=',
1667
+ active:'=',
1668
+ select:'&'
1669
+ },
1670
+ replace:true,
1671
+ templateUrl:'template/typeahead/typeahead.html',
1672
+ link:function (scope, element, attrs) {
1673
+
1674
+ scope.isOpen = function () {
1675
+ return scope.matches.length > 0;
1676
+ };
1677
+
1678
+ scope.isActive = function (matchIdx) {
1679
+ return scope.active == matchIdx;
1680
+ };
1681
+
1682
+ scope.selectActive = function (matchIdx) {
1683
+ scope.active = matchIdx;
1684
+ };
1685
+
1686
+ scope.selectMatch = function (activeIdx) {
1687
+ scope.select({activeIdx:activeIdx});
1688
+ };
1689
+ }
1690
+ };
1691
+ })
1692
+
1693
+ .filter('typeaheadHighlight', function() {
1694
+ return function(matchItem, query) {
1695
+ return (query) ? matchItem.replace(new RegExp(query, 'gi'), '<strong>$&</strong>') : query;
1696
+ };
1697
+ });
1322
1698
  angular.module("template/accordion/accordion-group.html", []).run(["$templateCache", function($templateCache){
1323
1699
  $templateCache.put("template/accordion/accordion-group.html",
1324
1700
  "<div class=\"accordion-group\">" +
1325
- " <div class=\"accordion-heading\" ><a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\">{{heading}}</a></div>" +
1701
+ " <div class=\"accordion-heading\" ><a class=\"accordion-toggle\" ng-click=\"isOpen = !isOpen\" accordion-transclude=\"heading\">{{heading}}</a></div>" +
1326
1702
  " <div class=\"accordion-body\" collapse=\"!isOpen\">" +
1327
1703
  " <div class=\"accordion-inner\" ng-transclude></div> </div>" +
1328
1704
  "</div>");
@@ -1344,6 +1720,9 @@ angular.module("template/alert/alert.html", []).run(["$templateCache", function(
1344
1720
  angular.module("template/carousel/carousel.html", []).run(["$templateCache", function($templateCache){
1345
1721
  $templateCache.put("template/carousel/carousel.html",
1346
1722
  "<div ng-mouseenter=\"pause()\" ng-mouseleave=\"play()\" class=\"carousel\">" +
1723
+ " <ol class=\"carousel-indicators\">" +
1724
+ " <li ng-repeat=\"slide in slides()\" ng-class=\"{active: isActive(slide)}\" ng-click=\"select(slide)\"></li>" +
1725
+ " </ol>" +
1347
1726
  " <div class=\"carousel-inner\" ng-transclude></div>" +
1348
1727
  " <a ng-click=\"prev()\" class=\"carousel-control left\">&lsaquo;</a>" +
1349
1728
  " <a ng-click=\"next()\" class=\"carousel-control right\">&rsaquo;</a>" +
@@ -1380,9 +1759,7 @@ angular.module("template/dialog/message.html", []).run(["$templateCache", functi
1380
1759
  angular.module("template/pagination/pagination.html", []).run(["$templateCache", function($templateCache){
1381
1760
  $templateCache.put("template/pagination/pagination.html",
1382
1761
  "<div class=\"pagination\"><ul>" +
1383
- " <li ng-class=\"{disabled: noPrevious()}\"><a ng-click=\"selectPrevious()\">{{previousText || 'Previous'}}</a></li>" +
1384
- " <li ng-repeat=\"page in pages\" ng-class=\"{active: isActive(page)}\"><a ng-click=\"selectPage(page)\">{{page}}</a></li>" +
1385
- " <li ng-class=\"{disabled: noNext()}\"><a ng-click=\"selectNext()\">{{nextText || 'Next'}}</a></li>" +
1762
+ " <li ng-repeat=\"page in pages\" ng-class=\"{active: page.active, disabled: page.disabled}\"><a ng-click=\"selectPage(page.number)\">{{page.text}}</a></li>" +
1386
1763
  " </ul>" +
1387
1764
  "</div>" +
1388
1765
  "");
@@ -1428,3 +1805,14 @@ angular.module("template/tooltip/tooltip-popup.html", []).run(["$templateCache",
1428
1805
  "</div>" +
1429
1806
  "");
1430
1807
  }]);
1808
+
1809
+ angular.module("template/typeahead/typeahead.html", []).run(["$templateCache", function($templateCache){
1810
+ $templateCache.put("template/typeahead/typeahead.html",
1811
+ "<div class=\"dropdown clearfix\" ng-class=\"{open: isOpen()}\">" +
1812
+ " <ul class=\"typeahead dropdown-menu\">" +
1813
+ " <li ng-repeat=\"match in matches\" ng-class=\"{active: isActive($index) }\" ng-mouseenter=\"selectActive($index)\">" +
1814
+ " <a tabindex=\"-1\" ng-click=\"selectMatch($index)\" ng-bind-html-unsafe=\"match.label | typeaheadHighlight:query\"></a>" +
1815
+ " </li>" +
1816
+ " </ul>" +
1817
+ "</div>");
1818
+ }]);