angular-ui-bootstrap-rails 0.1.1 → 0.2.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.
@@ -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
+ }]);