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