angular-ui-bootstrap-rails 0.4.0.0 → 0.5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,2924 +1,4 @@
1
- angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.position","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
2
- angular.module('ui.bootstrap.transition', [])
3
-
4
- /**
5
- * $transition service provides a consistent interface to trigger CSS 3 transitions and to be informed when they complete.
6
- * @param {DOMElement} element The DOMElement that will be animated.
7
- * @param {string|object|function} trigger The thing that will cause the transition to start:
8
- * - As a string, it represents the css class to be added to the element.
9
- * - As an object, it represents a hash of style attributes to be applied to the element.
10
- * - As a function, it represents a function to be called that will cause the transition to occur.
11
- * @return {Promise} A promise that is resolved when the transition finishes.
12
- */
13
- .factory('$transition', ['$q', '$timeout', '$rootScope', function($q, $timeout, $rootScope) {
14
-
15
- var $transition = function(element, trigger, options) {
16
- options = options || {};
17
- var deferred = $q.defer();
18
- var endEventName = $transition[options.animation ? "animationEndEventName" : "transitionEndEventName"];
19
-
20
- var transitionEndHandler = function(event) {
21
- $rootScope.$apply(function() {
22
- element.unbind(endEventName, transitionEndHandler);
23
- deferred.resolve(element);
24
- });
25
- };
26
-
27
- if (endEventName) {
28
- element.bind(endEventName, transitionEndHandler);
29
- }
30
-
31
- // Wrap in a timeout to allow the browser time to update the DOM before the transition is to occur
32
- $timeout(function() {
33
- if ( angular.isString(trigger) ) {
34
- element.addClass(trigger);
35
- } else if ( angular.isFunction(trigger) ) {
36
- trigger(element);
37
- } else if ( angular.isObject(trigger) ) {
38
- element.css(trigger);
39
- }
40
- //If browser does not support transitions, instantly resolve
41
- if ( !endEventName ) {
42
- deferred.resolve(element);
43
- }
44
- });
45
-
46
- // Add our custom cancel function to the promise that is returned
47
- // We can call this if we are about to run a new transition, which we know will prevent this transition from ending,
48
- // i.e. it will therefore never raise a transitionEnd event for that transition
49
- deferred.promise.cancel = function() {
50
- if ( endEventName ) {
51
- element.unbind(endEventName, transitionEndHandler);
52
- }
53
- deferred.reject('Transition cancelled');
54
- };
55
-
56
- return deferred.promise;
57
- };
58
-
59
- // Work out the name of the transitionEnd event
60
- var transElement = document.createElement('trans');
61
- var transitionEndEventNames = {
62
- 'WebkitTransition': 'webkitTransitionEnd',
63
- 'MozTransition': 'transitionend',
64
- 'OTransition': 'oTransitionEnd',
65
- 'transition': 'transitionend'
66
- };
67
- var animationEndEventNames = {
68
- 'WebkitTransition': 'webkitAnimationEnd',
69
- 'MozTransition': 'animationend',
70
- 'OTransition': 'oAnimationEnd',
71
- 'transition': 'animationend'
72
- };
73
- function findEndEventName(endEventNames) {
74
- for (var name in endEventNames){
75
- if (transElement.style[name] !== undefined) {
76
- return endEventNames[name];
77
- }
78
- }
79
- }
80
- $transition.transitionEndEventName = findEndEventName(transitionEndEventNames);
81
- $transition.animationEndEventName = findEndEventName(animationEndEventNames);
82
- return $transition;
83
- }]);
84
-
85
- angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
86
-
87
- // The collapsible directive indicates a block of html that will expand and collapse
88
- .directive('collapse', ['$transition', function($transition) {
89
- // CSS transitions don't work with height: auto, so we have to manually change the height to a
90
- // specific value and then once the animation completes, we can reset the height to auto.
91
- // Unfortunately if you do this while the CSS transitions are specified (i.e. in the CSS class
92
- // "collapse") then you trigger a change to height 0 in between.
93
- // The fix is to remove the "collapse" CSS class while changing the height back to auto - phew!
94
- var fixUpHeight = function(scope, element, height) {
95
- // We remove the collapse CSS class to prevent a transition when we change to height: auto
96
- element.removeClass('collapse');
97
- element.css({ height: height });
98
- // It appears that reading offsetWidth makes the browser realise that we have changed the
99
- // height already :-/
100
- var x = element[0].offsetWidth;
101
- element.addClass('collapse');
102
- };
103
-
104
- return {
105
- link: function(scope, element, attrs) {
106
-
107
- var isCollapsed;
108
- var initialAnimSkip = true;
109
- scope.$watch(function (){ return element[0].scrollHeight; }, function (value) {
110
- //The listener is called when scollHeight changes
111
- //It actually does on 2 scenarios:
112
- // 1. Parent is set to display none
113
- // 2. angular bindings inside are resolved
114
- //When we have a change of scrollHeight we are setting again the correct height if the group is opened
115
- if (element[0].scrollHeight !== 0) {
116
- if (!isCollapsed) {
117
- if (initialAnimSkip) {
118
- fixUpHeight(scope, element, element[0].scrollHeight + 'px');
119
- } else {
120
- fixUpHeight(scope, element, 'auto');
121
- }
122
- }
123
- }
124
- });
125
-
126
- scope.$watch(attrs.collapse, function(value) {
127
- if (value) {
128
- collapse();
129
- } else {
130
- expand();
131
- }
132
- });
133
-
134
-
135
- var currentTransition;
136
- var doTransition = function(change) {
137
- if ( currentTransition ) {
138
- currentTransition.cancel();
139
- }
140
- currentTransition = $transition(element,change);
141
- currentTransition.then(
142
- function() { currentTransition = undefined; },
143
- function() { currentTransition = undefined; }
144
- );
145
- return currentTransition;
146
- };
147
-
148
- var expand = function() {
149
- if (initialAnimSkip) {
150
- initialAnimSkip = false;
151
- if ( !isCollapsed ) {
152
- fixUpHeight(scope, element, 'auto');
153
- }
154
- } else {
155
- doTransition({ height : element[0].scrollHeight + 'px' })
156
- .then(function() {
157
- // This check ensures that we don't accidentally update the height if the user has closed
158
- // the group while the animation was still running
159
- if ( !isCollapsed ) {
160
- fixUpHeight(scope, element, 'auto');
161
- }
162
- });
163
- }
164
- isCollapsed = false;
165
- };
166
-
167
- var collapse = function() {
168
- isCollapsed = true;
169
- if (initialAnimSkip) {
170
- initialAnimSkip = false;
171
- fixUpHeight(scope, element, 0);
172
- } else {
173
- fixUpHeight(scope, element, element[0].scrollHeight + 'px');
174
- doTransition({'height':'0'});
175
- }
176
- };
177
- }
178
- };
179
- }]);
180
-
181
- angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
182
-
183
- .constant('accordionConfig', {
184
- closeOthers: true
185
- })
186
-
187
- .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
188
-
189
- // This array keeps track of the accordion groups
190
- this.groups = [];
191
-
192
- // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
193
- this.closeOthers = function(openGroup) {
194
- var closeOthers = angular.isDefined($attrs.closeOthers) ? $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
195
- if ( closeOthers ) {
196
- angular.forEach(this.groups, function (group) {
197
- if ( group !== openGroup ) {
198
- group.isOpen = false;
199
- }
200
- });
201
- }
202
- };
203
-
204
- // This is called from the accordion-group directive to add itself to the accordion
205
- this.addGroup = function(groupScope) {
206
- var that = this;
207
- this.groups.push(groupScope);
208
-
209
- groupScope.$on('$destroy', function (event) {
210
- that.removeGroup(groupScope);
211
- });
212
- };
213
-
214
- // This is called from the accordion-group directive when to remove itself
215
- this.removeGroup = function(group) {
216
- var index = this.groups.indexOf(group);
217
- if ( index !== -1 ) {
218
- this.groups.splice(this.groups.indexOf(group), 1);
219
- }
220
- };
221
-
222
- }])
223
-
224
- // The accordion directive simply sets up the directive controller
225
- // and adds an accordion CSS class to itself element.
226
- .directive('accordion', function () {
227
- return {
228
- restrict:'EA',
229
- controller:'AccordionController',
230
- transclude: true,
231
- replace: false,
232
- templateUrl: 'template/accordion/accordion.html'
233
- };
234
- })
235
-
236
- // The accordion-group directive indicates a block of html that will expand and collapse in an accordion
237
- .directive('accordionGroup', ['$parse', '$transition', '$timeout', function($parse, $transition, $timeout) {
238
- return {
239
- require:'^accordion', // We need this directive to be inside an accordion
240
- restrict:'EA',
241
- transclude:true, // It transcludes the contents of the directive into the template
242
- replace: true, // The element containing the directive will be replaced with the template
243
- templateUrl:'template/accordion/accordion-group.html',
244
- scope:{ heading:'@' }, // Create an isolated scope and interpolate the heading attribute onto this scope
245
- controller: ['$scope', function($scope) {
246
- this.setHeading = function(element) {
247
- this.heading = element;
248
- };
249
- }],
250
- link: function(scope, element, attrs, accordionCtrl) {
251
- var getIsOpen, setIsOpen;
252
-
253
- accordionCtrl.addGroup(scope);
254
-
255
- scope.isOpen = false;
256
-
257
- if ( attrs.isOpen ) {
258
- getIsOpen = $parse(attrs.isOpen);
259
- setIsOpen = getIsOpen.assign;
260
-
261
- scope.$watch(
262
- function watchIsOpen() { return getIsOpen(scope.$parent); },
263
- function updateOpen(value) { scope.isOpen = value; }
264
- );
265
-
266
- scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false;
267
- }
268
-
269
- scope.$watch('isOpen', function(value) {
270
- if ( value ) {
271
- accordionCtrl.closeOthers(scope);
272
- }
273
- if ( setIsOpen ) {
274
- setIsOpen(scope.$parent, value);
275
- }
276
- });
277
- }
278
- };
279
- }])
280
-
281
- // Use accordion-heading below an accordion-group to provide a heading containing HTML
282
- // <accordion-group>
283
- // <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
284
- // </accordion-group>
285
- .directive('accordionHeading', function() {
286
- return {
287
- restrict: 'EA',
288
- transclude: true, // Grab the contents to be used as the heading
289
- template: '', // In effect remove this element!
290
- replace: true,
291
- require: '^accordionGroup',
292
- compile: function(element, attr, transclude) {
293
- return function link(scope, element, attr, accordionGroupCtrl) {
294
- // Pass the heading to the accordion-group controller
295
- // so that it can be transcluded into the right place in the template
296
- // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
297
- accordionGroupCtrl.setHeading(transclude(scope, function() {}));
298
- };
299
- }
300
- };
301
- })
302
-
303
- // Use in the accordion-group template to indicate where you want the heading to be transcluded
304
- // You must provide the property on the accordion-group controller that will hold the transcluded element
305
- // <div class="accordion-group">
306
- // <div class="accordion-heading" ><a ... accordion-transclude="heading">...</a></div>
307
- // ...
308
- // </div>
309
- .directive('accordionTransclude', function() {
310
- return {
311
- require: '^accordionGroup',
312
- link: function(scope, element, attr, controller) {
313
- scope.$watch(function() { return controller[attr.accordionTransclude]; }, function(heading) {
314
- if ( heading ) {
315
- element.html('');
316
- element.append(heading);
317
- }
318
- });
319
- }
320
- };
321
- });
322
-
323
- angular.module("ui.bootstrap.alert", []).directive('alert', function () {
324
- return {
325
- restrict:'EA',
326
- templateUrl:'template/alert/alert.html',
327
- transclude:true,
328
- replace:true,
329
- scope: {
330
- type: '=',
331
- close: '&'
332
- },
333
- link: function(scope, iElement, iAttrs, controller) {
334
- scope.closeable = "close" in iAttrs;
335
- }
336
- };
337
- });
338
-
339
- angular.module('ui.bootstrap.buttons', [])
340
-
341
- .constant('buttonConfig', {
342
- activeClass:'active',
343
- toggleEvent:'click'
344
- })
345
-
346
- .directive('btnRadio', ['buttonConfig', function (buttonConfig) {
347
- var activeClass = buttonConfig.activeClass || 'active';
348
- var toggleEvent = buttonConfig.toggleEvent || 'click';
349
-
350
- return {
351
-
352
- require:'ngModel',
353
- link:function (scope, element, attrs, ngModelCtrl) {
354
-
355
- //model -> UI
356
- ngModelCtrl.$render = function () {
357
- element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.btnRadio)));
358
- };
359
-
360
- //ui->model
361
- element.bind(toggleEvent, function () {
362
- if (!element.hasClass(activeClass)) {
363
- scope.$apply(function () {
364
- ngModelCtrl.$setViewValue(scope.$eval(attrs.btnRadio));
365
- ngModelCtrl.$render();
366
- });
367
- }
368
- });
369
- }
370
- };
371
- }])
372
-
373
- .directive('btnCheckbox', ['buttonConfig', function (buttonConfig) {
374
-
375
- var activeClass = buttonConfig.activeClass || 'active';
376
- var toggleEvent = buttonConfig.toggleEvent || 'click';
377
-
378
- return {
379
- require:'ngModel',
380
- link:function (scope, element, attrs, ngModelCtrl) {
381
-
382
- var trueValue = scope.$eval(attrs.btnCheckboxTrue);
383
- var falseValue = scope.$eval(attrs.btnCheckboxFalse);
384
-
385
- trueValue = angular.isDefined(trueValue) ? trueValue : true;
386
- falseValue = angular.isDefined(falseValue) ? falseValue : false;
387
-
388
- //model -> UI
389
- ngModelCtrl.$render = function () {
390
- element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, trueValue));
391
- };
392
-
393
- //ui->model
394
- element.bind(toggleEvent, function () {
395
- scope.$apply(function () {
396
- ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
397
- ngModelCtrl.$render();
398
- });
399
- });
400
- }
401
- };
402
- }]);
403
- /**
404
- * @ngdoc overview
405
- * @name ui.bootstrap.carousel
406
- *
407
- * @description
408
- * AngularJS version of an image carousel.
409
- *
410
- */
411
- angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
412
- .controller('CarouselController', ['$scope', '$timeout', '$transition', '$q', function ($scope, $timeout, $transition, $q) {
413
- var self = this,
414
- slides = self.slides = [],
415
- currentIndex = -1,
416
- currentTimeout, isPlaying;
417
- self.currentSlide = null;
418
-
419
- /* direction: "prev" or "next" */
420
- self.select = function(nextSlide, direction) {
421
- var nextIndex = slides.indexOf(nextSlide);
422
- //Decide direction if it's not given
423
- if (direction === undefined) {
424
- direction = nextIndex > currentIndex ? "next" : "prev";
425
- }
426
- if (nextSlide && nextSlide !== self.currentSlide) {
427
- if ($scope.$currentTransition) {
428
- $scope.$currentTransition.cancel();
429
- //Timeout so ng-class in template has time to fix classes for finished slide
430
- $timeout(goNext);
431
- } else {
432
- goNext();
433
- }
434
- }
435
- function goNext() {
436
- //If we have a slide to transition from and we have a transition type and we're allowed, go
437
- if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
438
- //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
439
- nextSlide.$element.addClass(direction);
440
- nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow
441
-
442
- //Set all other slides to stop doing their stuff for the new transition
443
- angular.forEach(slides, function(slide) {
444
- angular.extend(slide, {direction: '', entering: false, leaving: false, active: false});
445
- });
446
- angular.extend(nextSlide, {direction: direction, active: true, entering: true});
447
- angular.extend(self.currentSlide||{}, {direction: direction, leaving: true});
448
-
449
- $scope.$currentTransition = $transition(nextSlide.$element, {});
450
- //We have to create new pointers inside a closure since next & current will change
451
- (function(next,current) {
452
- $scope.$currentTransition.then(
453
- function(){ transitionDone(next, current); },
454
- function(){ transitionDone(next, current); }
455
- );
456
- }(nextSlide, self.currentSlide));
457
- } else {
458
- transitionDone(nextSlide, self.currentSlide);
459
- }
460
- self.currentSlide = nextSlide;
461
- currentIndex = nextIndex;
462
- //every time you change slides, reset the timer
463
- restartTimer();
464
- }
465
- function transitionDone(next, current) {
466
- angular.extend(next, {direction: '', active: true, leaving: false, entering: false});
467
- angular.extend(current||{}, {direction: '', active: false, leaving: false, entering: false});
468
- $scope.$currentTransition = null;
469
- }
470
- };
471
-
472
- /* Allow outside people to call indexOf on slides array */
473
- self.indexOfSlide = function(slide) {
474
- return slides.indexOf(slide);
475
- };
476
-
477
- $scope.next = function() {
478
- var newIndex = (currentIndex + 1) % slides.length;
479
-
480
- //Prevent this user-triggered transition from occurring if there is already one in progress
481
- if (!$scope.$currentTransition) {
482
- return self.select(slides[newIndex], 'next');
483
- }
484
- };
485
-
486
- $scope.prev = function() {
487
- var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
488
-
489
- //Prevent this user-triggered transition from occurring if there is already one in progress
490
- if (!$scope.$currentTransition) {
491
- return self.select(slides[newIndex], 'prev');
492
- }
493
- };
494
-
495
- $scope.select = function(slide) {
496
- self.select(slide);
497
- };
498
-
499
- $scope.isActive = function(slide) {
500
- return self.currentSlide === slide;
501
- };
502
-
503
- $scope.slides = function() {
504
- return slides;
505
- };
506
-
507
- $scope.$watch('interval', restartTimer);
508
- function restartTimer() {
509
- if (currentTimeout) {
510
- $timeout.cancel(currentTimeout);
511
- }
512
- function go() {
513
- if (isPlaying) {
514
- $scope.next();
515
- restartTimer();
516
- } else {
517
- $scope.pause();
518
- }
519
- }
520
- var interval = +$scope.interval;
521
- if (!isNaN(interval) && interval>=0) {
522
- currentTimeout = $timeout(go, interval);
523
- }
524
- }
525
- $scope.play = function() {
526
- if (!isPlaying) {
527
- isPlaying = true;
528
- restartTimer();
529
- }
530
- };
531
- $scope.pause = function() {
532
- if (!$scope.noPause) {
533
- isPlaying = false;
534
- if (currentTimeout) {
535
- $timeout.cancel(currentTimeout);
536
- }
537
- }
538
- };
539
-
540
- self.addSlide = function(slide, element) {
541
- slide.$element = element;
542
- slides.push(slide);
543
- //if this is the first slide or the slide is set to active, select it
544
- if(slides.length === 1 || slide.active) {
545
- self.select(slides[slides.length-1]);
546
- if (slides.length == 1) {
547
- $scope.play();
548
- }
549
- } else {
550
- slide.active = false;
551
- }
552
- };
553
-
554
- self.removeSlide = function(slide) {
555
- //get the index of the slide inside the carousel
556
- var index = slides.indexOf(slide);
557
- slides.splice(index, 1);
558
- if (slides.length > 0 && slide.active) {
559
- if (index >= slides.length) {
560
- self.select(slides[index-1]);
561
- } else {
562
- self.select(slides[index]);
563
- }
564
- } else if (currentIndex > index) {
565
- currentIndex--;
566
- }
567
- };
568
- }])
569
-
570
- /**
571
- * @ngdoc directive
572
- * @name ui.bootstrap.carousel.directive:carousel
573
- * @restrict EA
574
- *
575
- * @description
576
- * Carousel is the outer container for a set of image 'slides' to showcase.
577
- *
578
- * @param {number=} interval The time, in milliseconds, that it will take the carousel to go to the next slide.
579
- * @param {boolean=} noTransition Whether to disable transitions on the carousel.
580
- * @param {boolean=} noPause Whether to disable pausing on the carousel (by default, the carousel interval pauses on hover).
581
- *
582
- * @example
583
- <example module="ui.bootstrap">
584
- <file name="index.html">
585
- <carousel>
586
- <slide>
587
- <img src="http://placekitten.com/150/150" style="margin:auto;">
588
- <div class="carousel-caption">
589
- <p>Beautiful!</p>
590
- </div>
591
- </slide>
592
- <slide>
593
- <img src="http://placekitten.com/100/150" style="margin:auto;">
594
- <div class="carousel-caption">
595
- <p>D'aww!</p>
596
- </div>
597
- </slide>
598
- </carousel>
599
- </file>
600
- <file name="demo.css">
601
- .carousel-indicators {
602
- top: auto;
603
- bottom: 15px;
604
- }
605
- </file>
606
- </example>
607
- */
608
- .directive('carousel', [function() {
609
- return {
610
- restrict: 'EA',
611
- transclude: true,
612
- replace: true,
613
- controller: 'CarouselController',
614
- require: 'carousel',
615
- templateUrl: 'template/carousel/carousel.html',
616
- scope: {
617
- interval: '=',
618
- noTransition: '=',
619
- noPause: '='
620
- }
621
- };
622
- }])
623
-
624
- /**
625
- * @ngdoc directive
626
- * @name ui.bootstrap.carousel.directive:slide
627
- * @restrict EA
628
- *
629
- * @description
630
- * Creates a slide inside a {@link ui.bootstrap.carousel.directive:carousel carousel}. Must be placed as a child of a carousel element.
631
- *
632
- * @param {boolean=} active Model binding, whether or not this slide is currently active.
633
- *
634
- * @example
635
- <example module="ui.bootstrap">
636
- <file name="index.html">
637
- <div ng-controller="CarouselDemoCtrl">
638
- <carousel>
639
- <slide ng-repeat="slide in slides" active="slide.active">
640
- <img ng-src="{{slide.image}}" style="margin:auto;">
641
- <div class="carousel-caption">
642
- <h4>Slide {{$index}}</h4>
643
- <p>{{slide.text}}</p>
644
- </div>
645
- </slide>
646
- </carousel>
647
- <div class="row-fluid">
648
- <div class="span6">
649
- <ul>
650
- <li ng-repeat="slide in slides">
651
- <button class="btn btn-mini" ng-class="{'btn-info': !slide.active, 'btn-success': slide.active}" ng-disabled="slide.active" ng-click="slide.active = true">select</button>
652
- {{$index}}: {{slide.text}}
653
- </li>
654
- </ul>
655
- <a class="btn" ng-click="addSlide()">Add Slide</a>
656
- </div>
657
- <div class="span6">
658
- Interval, in milliseconds: <input type="number" ng-model="myInterval">
659
- <br />Enter a negative number to stop the interval.
660
- </div>
661
- </div>
662
- </div>
663
- </file>
664
- <file name="script.js">
665
- function CarouselDemoCtrl($scope) {
666
- $scope.myInterval = 5000;
667
- var slides = $scope.slides = [];
668
- $scope.addSlide = function() {
669
- var newWidth = 200 + ((slides.length + (25 * slides.length)) % 150);
670
- slides.push({
671
- image: 'http://placekitten.com/' + newWidth + '/200',
672
- text: ['More','Extra','Lots of','Surplus'][slides.length % 4] + ' '
673
- ['Cats', 'Kittys', 'Felines', 'Cutes'][slides.length % 4]
674
- });
675
- };
676
- for (var i=0; i<4; i++) $scope.addSlide();
677
- }
678
- </file>
679
- <file name="demo.css">
680
- .carousel-indicators {
681
- top: auto;
682
- bottom: 15px;
683
- }
684
- </file>
685
- </example>
686
- */
687
-
688
- .directive('slide', ['$parse', function($parse) {
689
- return {
690
- require: '^carousel',
691
- restrict: 'EA',
692
- transclude: true,
693
- replace: true,
694
- templateUrl: 'template/carousel/slide.html',
695
- scope: {
696
- },
697
- link: function (scope, element, attrs, carouselCtrl) {
698
- //Set up optional 'active' = binding
699
- if (attrs.active) {
700
- var getActive = $parse(attrs.active);
701
- var setActive = getActive.assign;
702
- var lastValue = scope.active = getActive(scope.$parent);
703
- scope.$watch(function parentActiveWatch() {
704
- var parentActive = getActive(scope.$parent);
705
-
706
- if (parentActive !== scope.active) {
707
- // we are out of sync and need to copy
708
- if (parentActive !== lastValue) {
709
- // parent changed and it has precedence
710
- lastValue = scope.active = parentActive;
711
- } else {
712
- // if the parent can be assigned then do so
713
- setActive(scope.$parent, parentActive = lastValue = scope.active);
714
- }
715
- }
716
- return parentActive;
717
- });
718
- }
719
-
720
- carouselCtrl.addSlide(scope, element);
721
- //when the scope is destroyed then remove the slide from the current slides array
722
- scope.$on('$destroy', function() {
723
- carouselCtrl.removeSlide(scope);
724
- });
725
-
726
- scope.$watch('active', function(active) {
727
- if (active) {
728
- carouselCtrl.select(scope);
729
- }
730
- });
731
- }
732
- };
733
- }]);
734
-
735
- angular.module('ui.bootstrap.datepicker', [])
736
-
737
- .constant('datepickerConfig', {
738
- dayFormat: 'dd',
739
- monthFormat: 'MMMM',
740
- yearFormat: 'yyyy',
741
- dayHeaderFormat: 'EEE',
742
- dayTitleFormat: 'MMMM yyyy',
743
- monthTitleFormat: 'yyyy',
744
- showWeeks: true,
745
- startingDay: 0,
746
- yearRange: 20
747
- })
748
-
749
- .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', function (dateFilter, $parse, datepickerConfig) {
750
- return {
751
- restrict: 'EA',
752
- replace: true,
753
- scope: {
754
- model: '=ngModel',
755
- dateDisabled: '&'
756
- },
757
- templateUrl: 'template/datepicker/datepicker.html',
758
- link: function(scope, element, attrs) {
759
- scope.mode = 'day'; // Initial mode
760
-
761
- // Configuration parameters
762
- var selected = new Date(), showWeeks, minDate, maxDate, format = {};
763
- format.day = angular.isDefined(attrs.dayFormat) ? scope.$eval(attrs.dayFormat) : datepickerConfig.dayFormat;
764
- format.month = angular.isDefined(attrs.monthFormat) ? scope.$eval(attrs.monthFormat) : datepickerConfig.monthFormat;
765
- format.year = angular.isDefined(attrs.yearFormat) ? scope.$eval(attrs.yearFormat) : datepickerConfig.yearFormat;
766
- format.dayHeader = angular.isDefined(attrs.dayHeaderFormat) ? scope.$eval(attrs.dayHeaderFormat) : datepickerConfig.dayHeaderFormat;
767
- format.dayTitle = angular.isDefined(attrs.dayTitleFormat) ? scope.$eval(attrs.dayTitleFormat) : datepickerConfig.dayTitleFormat;
768
- format.monthTitle = angular.isDefined(attrs.monthTitleFormat) ? scope.$eval(attrs.monthTitleFormat) : datepickerConfig.monthTitleFormat;
769
- var startingDay = angular.isDefined(attrs.startingDay) ? scope.$eval(attrs.startingDay) : datepickerConfig.startingDay;
770
- var yearRange = angular.isDefined(attrs.yearRange) ? scope.$eval(attrs.yearRange) : datepickerConfig.yearRange;
771
-
772
- if (attrs.showWeeks) {
773
- scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
774
- showWeeks = !! value;
775
- updateShowWeekNumbers();
776
- });
777
- } else {
778
- showWeeks = datepickerConfig.showWeeks;
779
- updateShowWeekNumbers();
780
- }
781
-
782
- if (attrs.min) {
783
- scope.$parent.$watch($parse(attrs.min), function(value) {
784
- minDate = new Date(value);
785
- refill();
786
- });
787
- }
788
- if (attrs.max) {
789
- scope.$parent.$watch($parse(attrs.max), function(value) {
790
- maxDate = new Date(value);
791
- refill();
792
- });
793
- }
794
-
795
- function updateCalendar (rows, labels, title) {
796
- scope.rows = rows;
797
- scope.labels = labels;
798
- scope.title = title;
799
- }
800
-
801
- // Define whether the week number are visible
802
- function updateShowWeekNumbers() {
803
- scope.showWeekNumbers = ( scope.mode === 'day' && showWeeks );
804
- }
805
-
806
- function compare( date1, date2 ) {
807
- if ( scope.mode === 'year') {
808
- return date2.getFullYear() - date1.getFullYear();
809
- } else if ( scope.mode === 'month' ) {
810
- return new Date( date2.getFullYear(), date2.getMonth() ) - new Date( date1.getFullYear(), date1.getMonth() );
811
- } else if ( scope.mode === 'day' ) {
812
- return (new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) - new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) );
813
- }
814
- }
815
-
816
- function isDisabled(date) {
817
- return ((minDate && compare(date, minDate) > 0) || (maxDate && compare(date, maxDate) < 0) || (scope.dateDisabled && scope.dateDisabled({ date: date, mode: scope.mode })));
818
- }
819
-
820
- // Split array into smaller arrays
821
- var split = function(a, size) {
822
- var arrays = [];
823
- while (a.length > 0) {
824
- arrays.push(a.splice(0, size));
825
- }
826
- return arrays;
827
- };
828
- var getDaysInMonth = function( year, month ) {
829
- return new Date(year, month + 1, 0).getDate();
830
- };
831
-
832
- var fill = {
833
- day: function() {
834
- var days = [], labels = [], lastDate = null;
835
-
836
- function addDays( dt, n, isCurrentMonth ) {
837
- for (var i =0; i < n; i ++) {
838
- days.push( {date: new Date(dt), isCurrent: isCurrentMonth, isSelected: isSelected(dt), label: dateFilter(dt, format.day), disabled: isDisabled(dt) } );
839
- dt.setDate( dt.getDate() + 1 );
840
- }
841
- lastDate = dt;
842
- }
843
-
844
- var d = new Date(selected);
845
- d.setDate(1);
846
-
847
- var difference = startingDay - d.getDay();
848
- var numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference;
849
-
850
- if ( numDisplayedFromPreviousMonth > 0 ) {
851
- d.setDate( - numDisplayedFromPreviousMonth + 1 );
852
- addDays(d, numDisplayedFromPreviousMonth, false);
853
- }
854
- addDays(lastDate || d, getDaysInMonth(selected.getFullYear(), selected.getMonth()), true);
855
- addDays(lastDate, (7 - days.length % 7) % 7, false);
856
-
857
- // Day labels
858
- for (i = 0; i < 7; i++) {
859
- labels.push( dateFilter(days[i].date, format.dayHeader) );
860
- }
861
- updateCalendar( split( days, 7 ), labels, dateFilter(selected, format.dayTitle) );
862
- },
863
- month: function() {
864
- var months = [], i = 0, year = selected.getFullYear();
865
- while ( i < 12 ) {
866
- var dt = new Date(year, i++, 1);
867
- months.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.month), disabled: isDisabled(dt)} );
868
- }
869
- updateCalendar( split( months, 3 ), [], dateFilter(selected, format.monthTitle) );
870
- },
871
- year: function() {
872
- var years = [], year = parseInt((selected.getFullYear() - 1) / yearRange, 10) * yearRange + 1;
873
- for ( var i = 0; i < yearRange; i++ ) {
874
- var dt = new Date(year + i, 0, 1);
875
- years.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.year), disabled: isDisabled(dt)} );
876
- }
877
- var title = years[0].label + ' - ' + years[years.length - 1].label;
878
- updateCalendar( split( years, 5 ), [], title );
879
- }
880
- };
881
- var refill = function() {
882
- fill[scope.mode]();
883
- };
884
- var isSelected = function( dt ) {
885
- if ( scope.model && scope.model.getFullYear() === dt.getFullYear() ) {
886
- if ( scope.mode === 'year' ) {
887
- return true;
888
- }
889
- if ( scope.model.getMonth() === dt.getMonth() ) {
890
- return ( scope.mode === 'month' || (scope.mode === 'day' && scope.model.getDate() === dt.getDate()) );
891
- }
892
- }
893
- return false;
894
- };
895
-
896
- scope.$watch('model', function ( dt, olddt ) {
897
- if ( angular.isDate(dt) ) {
898
- selected = angular.copy(dt);
899
- }
900
-
901
- if ( ! angular.equals(dt, olddt) ) {
902
- refill();
903
- }
904
- });
905
- scope.$watch('mode', function() {
906
- updateShowWeekNumbers();
907
- refill();
908
- });
909
-
910
- scope.select = function( dt ) {
911
- selected = new Date(dt);
912
-
913
- if ( scope.mode === 'year' ) {
914
- scope.mode = 'month';
915
- selected.setFullYear( dt.getFullYear() );
916
- } else if ( scope.mode === 'month' ) {
917
- scope.mode = 'day';
918
- selected.setMonth( dt.getMonth() );
919
- } else if ( scope.mode === 'day' ) {
920
- scope.model = new Date(selected);
921
- }
922
- };
923
- scope.move = function(step) {
924
- if (scope.mode === 'day') {
925
- selected.setMonth( selected.getMonth() + step );
926
- } else if (scope.mode === 'month') {
927
- selected.setFullYear( selected.getFullYear() + step );
928
- } else if (scope.mode === 'year') {
929
- selected.setFullYear( selected.getFullYear() + step * yearRange );
930
- }
931
- refill();
932
- };
933
- scope.toggleMode = function() {
934
- scope.mode = ( scope.mode === 'day' ) ? 'month' : ( scope.mode === 'month' ) ? 'year' : 'day';
935
- };
936
- scope.getWeekNumber = function(row) {
937
- if ( scope.mode !== 'day' || ! scope.showWeekNumbers || row.length !== 7 ) {
938
- return;
939
- }
940
-
941
- var index = ( startingDay > 4 ) ? 11 - startingDay : 4 - startingDay; // Thursday
942
- var d = new Date( row[ index ].date );
943
- d.setHours(0, 0, 0);
944
- return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 86400000) + 1) / 7); // 86400000 = 1000*60*60*24;
945
- };
946
- }
947
- };
948
- }]);
949
- // The `$dialogProvider` can be used to configure global defaults for your
950
- // `$dialog` service.
951
- var dialogModule = angular.module('ui.bootstrap.dialog', ['ui.bootstrap.transition']);
952
-
953
- dialogModule.controller('MessageBoxController', ['$scope', 'dialog', 'model', function($scope, dialog, model){
954
- $scope.title = model.title;
955
- $scope.message = model.message;
956
- $scope.buttons = model.buttons;
957
- $scope.close = function(res){
958
- dialog.close(res);
959
- };
960
- }]);
961
-
962
- dialogModule.provider("$dialog", function(){
963
-
964
- // The default options for all dialogs.
965
- var defaults = {
966
- backdrop: true,
967
- dialogClass: 'modal',
968
- backdropClass: 'modal-backdrop',
969
- transitionClass: 'fade',
970
- triggerClass: 'in',
971
- resolve:{},
972
- backdropFade: false,
973
- dialogFade:false,
974
- keyboard: true, // close with esc key
975
- backdropClick: true // only in conjunction with backdrop=true
976
- /* other options: template, templateUrl, controller */
977
- };
978
-
979
- var globalOptions = {};
980
-
981
- var activeBackdrops = {value : 0};
982
-
983
- // The `options({})` allows global configuration of all dialogs in the application.
984
- //
985
- // var app = angular.module('App', ['ui.bootstrap.dialog'], function($dialogProvider){
986
- // // don't close dialog when backdrop is clicked by default
987
- // $dialogProvider.options({backdropClick: false});
988
- // });
989
- this.options = function(value){
990
- globalOptions = value;
991
- };
992
-
993
- // Returns the actual `$dialog` service that is injected in controllers
994
- this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
995
- function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
996
-
997
- var body = $document.find('body');
998
-
999
- function createElement(clazz) {
1000
- var el = angular.element("<div>");
1001
- el.addClass(clazz);
1002
- return el;
1003
- }
1004
-
1005
- // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
1006
- // containing at lest template or templateUrl and controller:
1007
- //
1008
- // var d = new Dialog({templateUrl: 'foo.html', controller: 'BarController'});
1009
- //
1010
- // Dialogs can also be created using templateUrl and controller as distinct arguments:
1011
- //
1012
- // var d = new Dialog('path/to/dialog.html', MyDialogController);
1013
- function Dialog(opts) {
1014
-
1015
- var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
1016
- this._open = false;
1017
-
1018
- this.backdropEl = createElement(options.backdropClass);
1019
- if(options.backdropFade){
1020
- this.backdropEl.addClass(options.transitionClass);
1021
- this.backdropEl.removeClass(options.triggerClass);
1022
- }
1023
-
1024
- this.modalEl = createElement(options.dialogClass);
1025
- if(options.dialogFade){
1026
- this.modalEl.addClass(options.transitionClass);
1027
- this.modalEl.removeClass(options.triggerClass);
1028
- }
1029
-
1030
- this.handledEscapeKey = function(e) {
1031
- if (e.which === 27) {
1032
- self.close();
1033
- e.preventDefault();
1034
- self.$scope.$apply();
1035
- }
1036
- };
1037
-
1038
- this.handleBackDropClick = function(e) {
1039
- self.close();
1040
- e.preventDefault();
1041
- self.$scope.$apply();
1042
- };
1043
-
1044
- this.handleLocationChange = function() {
1045
- self.close();
1046
- };
1047
- }
1048
-
1049
- // The `isOpen()` method returns wether the dialog is currently visible.
1050
- Dialog.prototype.isOpen = function(){
1051
- return this._open;
1052
- };
1053
-
1054
- // The `open(templateUrl, controller)` method opens the dialog.
1055
- // Use the `templateUrl` and `controller` arguments if specifying them at dialog creation time is not desired.
1056
- Dialog.prototype.open = function(templateUrl, controller){
1057
- var self = this, options = this.options;
1058
-
1059
- if(templateUrl){
1060
- options.templateUrl = templateUrl;
1061
- }
1062
- if(controller){
1063
- options.controller = controller;
1064
- }
1065
-
1066
- if(!(options.template || options.templateUrl)) {
1067
- throw new Error('Dialog.open expected template or templateUrl, neither found. Use options or open method to specify them.');
1068
- }
1069
-
1070
- this._loadResolves().then(function(locals) {
1071
- var $scope = locals.$scope = self.$scope = locals.$scope ? locals.$scope : $rootScope.$new();
1072
-
1073
- self.modalEl.html(locals.$template);
1074
-
1075
- if (self.options.controller) {
1076
- var ctrl = $controller(self.options.controller, locals);
1077
- self.modalEl.children().data('ngControllerController', ctrl);
1078
- }
1079
-
1080
- $compile(self.modalEl)($scope);
1081
- self._addElementsToDom();
1082
-
1083
- // trigger tranisitions
1084
- setTimeout(function(){
1085
- if(self.options.dialogFade){ self.modalEl.addClass(self.options.triggerClass); }
1086
- if(self.options.backdropFade){ self.backdropEl.addClass(self.options.triggerClass); }
1087
- });
1088
-
1089
- self._bindEvents();
1090
- });
1091
-
1092
- this.deferred = $q.defer();
1093
- return this.deferred.promise;
1094
- };
1095
-
1096
- // closes the dialog and resolves the promise returned by the `open` method with the specified result.
1097
- Dialog.prototype.close = function(result){
1098
- var self = this;
1099
- var fadingElements = this._getFadingElements();
1100
-
1101
- if(fadingElements.length > 0){
1102
- for (var i = fadingElements.length - 1; i >= 0; i--) {
1103
- $transition(fadingElements[i], removeTriggerClass).then(onCloseComplete);
1104
- }
1105
- return;
1106
- }
1107
-
1108
- this._onCloseComplete(result);
1109
-
1110
- function removeTriggerClass(el){
1111
- el.removeClass(self.options.triggerClass);
1112
- }
1113
-
1114
- function onCloseComplete(){
1115
- if(self._open){
1116
- self._onCloseComplete(result);
1117
- }
1118
- }
1119
- };
1120
-
1121
- Dialog.prototype._getFadingElements = function(){
1122
- var elements = [];
1123
- if(this.options.dialogFade){
1124
- elements.push(this.modalEl);
1125
- }
1126
- if(this.options.backdropFade){
1127
- elements.push(this.backdropEl);
1128
- }
1129
-
1130
- return elements;
1131
- };
1132
-
1133
- Dialog.prototype._bindEvents = function() {
1134
- if(this.options.keyboard){ body.bind('keydown', this.handledEscapeKey); }
1135
- if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.bind('click', this.handleBackDropClick); }
1136
- };
1137
-
1138
- Dialog.prototype._unbindEvents = function() {
1139
- if(this.options.keyboard){ body.unbind('keydown', this.handledEscapeKey); }
1140
- if(this.options.backdrop && this.options.backdropClick){ this.backdropEl.unbind('click', this.handleBackDropClick); }
1141
- };
1142
-
1143
- Dialog.prototype._onCloseComplete = function(result) {
1144
- this._removeElementsFromDom();
1145
- this._unbindEvents();
1146
-
1147
- this.deferred.resolve(result);
1148
- };
1149
-
1150
- Dialog.prototype._addElementsToDom = function(){
1151
- body.append(this.modalEl);
1152
-
1153
- if(this.options.backdrop) {
1154
- if (activeBackdrops.value === 0) {
1155
- body.append(this.backdropEl);
1156
- }
1157
- activeBackdrops.value++;
1158
- }
1159
-
1160
- this._open = true;
1161
- };
1162
-
1163
- Dialog.prototype._removeElementsFromDom = function(){
1164
- this.modalEl.remove();
1165
-
1166
- if(this.options.backdrop) {
1167
- activeBackdrops.value--;
1168
- if (activeBackdrops.value === 0) {
1169
- this.backdropEl.remove();
1170
- }
1171
- }
1172
- this._open = false;
1173
- };
1174
-
1175
- // Loads all `options.resolve` members to be used as locals for the controller associated with the dialog.
1176
- Dialog.prototype._loadResolves = function(){
1177
- var values = [], keys = [], templatePromise, self = this;
1178
-
1179
- if (this.options.template) {
1180
- templatePromise = $q.when(this.options.template);
1181
- } else if (this.options.templateUrl) {
1182
- templatePromise = $http.get(this.options.templateUrl, {cache:$templateCache})
1183
- .then(function(response) { return response.data; });
1184
- }
1185
-
1186
- angular.forEach(this.options.resolve || [], function(value, key) {
1187
- keys.push(key);
1188
- values.push(angular.isString(value) ? $injector.get(value) : $injector.invoke(value));
1189
- });
1190
-
1191
- keys.push('$template');
1192
- values.push(templatePromise);
1193
-
1194
- return $q.all(values).then(function(values) {
1195
- var locals = {};
1196
- angular.forEach(values, function(value, index) {
1197
- locals[keys[index]] = value;
1198
- });
1199
- locals.dialog = self;
1200
- return locals;
1201
- });
1202
- };
1203
-
1204
- // The actual `$dialog` service that is injected in controllers.
1205
- return {
1206
- // Creates a new `Dialog` with the specified options.
1207
- dialog: function(opts){
1208
- return new Dialog(opts);
1209
- },
1210
- // creates a new `Dialog` tied to the default message box template and controller.
1211
- //
1212
- // Arguments `title` and `message` are rendered in the modal header and body sections respectively.
1213
- // The `buttons` array holds an object with the following members for each button to include in the
1214
- // modal footer section:
1215
- //
1216
- // * `result`: the result to pass to the `close` method of the dialog when the button is clicked
1217
- // * `label`: the label of the button
1218
- // * `cssClass`: additional css class(es) to apply to the button for styling
1219
- messageBox: function(title, message, buttons){
1220
- return new Dialog({templateUrl: 'template/dialog/message.html', controller: 'MessageBoxController', resolve:
1221
- {model: function() {
1222
- return {
1223
- title: title,
1224
- message: message,
1225
- buttons: buttons
1226
- };
1227
- }
1228
- }});
1229
- }
1230
- };
1231
- }];
1232
- });
1233
-
1234
- /*
1235
- * dropdownToggle - Provides dropdown menu functionality in place of bootstrap js
1236
- * @restrict class or attribute
1237
- * @example:
1238
- <li class="dropdown">
1239
- <a class="dropdown-toggle">My Dropdown Menu</a>
1240
- <ul class="dropdown-menu">
1241
- <li ng-repeat="choice in dropChoices">
1242
- <a ng-href="{{choice.href}}">{{choice.text}}</a>
1243
- </li>
1244
- </ul>
1245
- </li>
1246
- */
1247
-
1248
- angular.module('ui.bootstrap.dropdownToggle', []).directive('dropdownToggle', ['$document', '$location', function ($document, $location) {
1249
- var openElement = null,
1250
- closeMenu = angular.noop;
1251
- return {
1252
- restrict: 'CA',
1253
- link: function(scope, element, attrs) {
1254
- scope.$watch('$location.path', function() { closeMenu(); });
1255
- element.parent().bind('click', function() { closeMenu(); });
1256
- element.bind('click', function (event) {
1257
-
1258
- var elementWasOpen = (element === openElement);
1259
-
1260
- event.preventDefault();
1261
- event.stopPropagation();
1262
-
1263
- if (!!openElement) {
1264
- closeMenu();
1265
- }
1266
-
1267
- if (!elementWasOpen) {
1268
- element.parent().addClass('open');
1269
- openElement = element;
1270
- closeMenu = function (event) {
1271
- if (event) {
1272
- event.preventDefault();
1273
- event.stopPropagation();
1274
- }
1275
- $document.unbind('click', closeMenu);
1276
- element.parent().removeClass('open');
1277
- closeMenu = angular.noop;
1278
- openElement = null;
1279
- };
1280
- $document.bind('click', closeMenu);
1281
- }
1282
- });
1283
- }
1284
- };
1285
- }]);
1286
- angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
1287
- .directive('modal', ['$parse', '$dialog', function($parse, $dialog) {
1288
- return {
1289
- restrict: 'EA',
1290
- terminal: true,
1291
- link: function(scope, elm, attrs) {
1292
- var opts = angular.extend({}, scope.$eval(attrs.uiOptions || attrs.bsOptions || attrs.options));
1293
- var shownExpr = attrs.modal || attrs.show;
1294
- var setClosed;
1295
-
1296
- // Create a dialog with the template as the contents of the directive
1297
- // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
1298
- opts = angular.extend(opts, {
1299
- template: elm.html(),
1300
- resolve: { $scope: function() { return scope; } }
1301
- });
1302
- var dialog = $dialog.dialog(opts);
1303
-
1304
- elm.remove();
1305
-
1306
- if (attrs.close) {
1307
- setClosed = function() {
1308
- $parse(attrs.close)(scope);
1309
- };
1310
- } else {
1311
- setClosed = function() {
1312
- if (angular.isFunction($parse(shownExpr).assign)) {
1313
- $parse(shownExpr).assign(scope, false);
1314
- }
1315
- };
1316
- }
1317
-
1318
- scope.$watch(shownExpr, function(isShown, oldShown) {
1319
- if (isShown) {
1320
- dialog.open().then(function(){
1321
- setClosed();
1322
- });
1323
- } else {
1324
- //Make sure it is not opened
1325
- if (dialog.isOpen()){
1326
- dialog.close();
1327
- }
1328
- }
1329
- });
1330
- }
1331
- };
1332
- }]);
1333
- angular.module('ui.bootstrap.pagination', [])
1334
-
1335
- .controller('PaginationController', ['$scope', function (scope) {
1336
-
1337
- scope.noPrevious = function() {
1338
- return scope.currentPage === 1;
1339
- };
1340
- scope.noNext = function() {
1341
- return scope.currentPage === scope.numPages;
1342
- };
1343
-
1344
- scope.isActive = function(page) {
1345
- return scope.currentPage === page;
1346
- };
1347
-
1348
- scope.selectPage = function(page) {
1349
- if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) {
1350
- scope.currentPage = page;
1351
- scope.onSelectPage({ page: page });
1352
- }
1353
- };
1354
- }])
1355
-
1356
- .constant('paginationConfig', {
1357
- boundaryLinks: false,
1358
- directionLinks: true,
1359
- firstText: 'First',
1360
- previousText: 'Previous',
1361
- nextText: 'Next',
1362
- lastText: 'Last',
1363
- rotate: true
1364
- })
1365
-
1366
- .directive('pagination', ['paginationConfig', function(paginationConfig) {
1367
- return {
1368
- restrict: 'EA',
1369
- scope: {
1370
- numPages: '=',
1371
- currentPage: '=',
1372
- maxSize: '=',
1373
- onSelectPage: '&'
1374
- },
1375
- controller: 'PaginationController',
1376
- templateUrl: 'template/pagination/pagination.html',
1377
- replace: true,
1378
- link: function(scope, element, attrs) {
1379
-
1380
- // Setup configuration parameters
1381
- var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
1382
- var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
1383
- var firstText = angular.isDefined(attrs.firstText) ? scope.$parent.$eval(attrs.firstText) : paginationConfig.firstText;
1384
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : paginationConfig.previousText;
1385
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : paginationConfig.nextText;
1386
- var lastText = angular.isDefined(attrs.lastText) ? scope.$parent.$eval(attrs.lastText) : paginationConfig.lastText;
1387
- var rotate = angular.isDefined(attrs.rotate) ? scope.$eval(attrs.rotate) : paginationConfig.rotate;
1388
-
1389
- // Create page object used in template
1390
- function makePage(number, text, isActive, isDisabled) {
1391
- return {
1392
- number: number,
1393
- text: text,
1394
- active: isActive,
1395
- disabled: isDisabled
1396
- };
1397
- }
1398
-
1399
- scope.$watch('numPages + currentPage + maxSize', function() {
1400
- scope.pages = [];
1401
-
1402
- // Default page limits
1403
- var startPage = 1, endPage = scope.numPages;
1404
- var isMaxSized = ( angular.isDefined(scope.maxSize) && scope.maxSize < scope.numPages );
1405
-
1406
- // recompute if maxSize
1407
- if ( isMaxSized ) {
1408
- if ( rotate ) {
1409
- // Current page is displayed in the middle of the visible ones
1410
- startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize/2), 1);
1411
- endPage = startPage + scope.maxSize - 1;
1412
-
1413
- // Adjust if limit is exceeded
1414
- if (endPage > scope.numPages) {
1415
- endPage = scope.numPages;
1416
- startPage = endPage - scope.maxSize + 1;
1417
- }
1418
- } else {
1419
- // Visible pages are paginated with maxSize
1420
- startPage = ((Math.ceil(scope.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
1421
-
1422
- // Adjust last page if limit is exceeded
1423
- endPage = Math.min(startPage + scope.maxSize - 1, scope.numPages);
1424
- }
1425
- }
1426
-
1427
- // Add page number links
1428
- for (var number = startPage; number <= endPage; number++) {
1429
- var page = makePage(number, number, scope.isActive(number), false);
1430
- scope.pages.push(page);
1431
- }
1432
-
1433
- // Add links to move between page sets
1434
- if ( isMaxSized && ! rotate ) {
1435
- if ( startPage > 1 ) {
1436
- var previousPageSet = makePage(startPage - 1, '...', false, false);
1437
- scope.pages.unshift(previousPageSet);
1438
- }
1439
-
1440
- if ( endPage < scope.numPages ) {
1441
- var nextPageSet = makePage(endPage + 1, '...', false, false);
1442
- scope.pages.push(nextPageSet);
1443
- }
1444
- }
1445
-
1446
- // Add previous & next links
1447
- if (directionLinks) {
1448
- var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
1449
- scope.pages.unshift(previousPage);
1450
-
1451
- var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
1452
- scope.pages.push(nextPage);
1453
- }
1454
-
1455
- // Add first & last links
1456
- if (boundaryLinks) {
1457
- var firstPage = makePage(1, firstText, false, scope.noPrevious());
1458
- scope.pages.unshift(firstPage);
1459
-
1460
- var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
1461
- scope.pages.push(lastPage);
1462
- }
1463
-
1464
- if ( scope.currentPage > scope.numPages ) {
1465
- scope.selectPage(scope.numPages);
1466
- }
1467
- });
1468
- }
1469
- };
1470
- }])
1471
-
1472
- .constant('pagerConfig', {
1473
- previousText: '« Previous',
1474
- nextText: 'Next »',
1475
- align: true
1476
- })
1477
-
1478
- .directive('pager', ['pagerConfig', function(config) {
1479
- return {
1480
- restrict: 'EA',
1481
- scope: {
1482
- numPages: '=',
1483
- currentPage: '=',
1484
- onSelectPage: '&'
1485
- },
1486
- controller: 'PaginationController',
1487
- templateUrl: 'template/pagination/pager.html',
1488
- replace: true,
1489
- link: function(scope, element, attrs, paginationCtrl) {
1490
-
1491
- // Setup configuration parameters
1492
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : config.previousText;
1493
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : config.nextText;
1494
- var align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : config.align;
1495
-
1496
- // Create page object used in template
1497
- function makePage(number, text, isDisabled, isPrevious, isNext) {
1498
- return {
1499
- number: number,
1500
- text: text,
1501
- disabled: isDisabled,
1502
- previous: ( align && isPrevious ),
1503
- next: ( align && isNext )
1504
- };
1505
- }
1506
-
1507
- scope.$watch('numPages + currentPage', function() {
1508
- scope.pages = [];
1509
-
1510
- // Add previous & next links
1511
- var previousPage = makePage(scope.currentPage - 1, previousText, scope.noPrevious(), true, false);
1512
- scope.pages.unshift(previousPage);
1513
-
1514
- var nextPage = makePage(scope.currentPage + 1, nextText, scope.noNext(), false, true);
1515
- scope.pages.push(nextPage);
1516
-
1517
- if ( scope.currentPage > scope.numPages ) {
1518
- scope.selectPage(scope.numPages);
1519
- }
1520
- });
1521
- }
1522
- };
1523
- }]);
1524
-
1525
- angular.module('ui.bootstrap.position', [])
1526
-
1527
- /**
1528
- * A set of utility methods that can be use to retrieve position of DOM elements.
1529
- * It is meant to be used where we need to absolute-position DOM elements in
1530
- * relation to other, existing elements (this is the case for tooltips, popovers,
1531
- * typeahead suggestions etc.).
1532
- */
1533
- .factory('$position', ['$document', '$window', function ($document, $window) {
1534
-
1535
- var mouseX, mouseY;
1536
-
1537
- $document.bind('mousemove', function mouseMoved(event) {
1538
- mouseX = event.pageX;
1539
- mouseY = event.pageY;
1540
- });
1541
-
1542
- function getStyle(el, cssprop) {
1543
- if (el.currentStyle) { //IE
1544
- return el.currentStyle[cssprop];
1545
- } else if ($window.getComputedStyle) {
1546
- return $window.getComputedStyle(el)[cssprop];
1547
- }
1548
- // finally try and get inline style
1549
- return el.style[cssprop];
1550
- }
1551
-
1552
- /**
1553
- * Checks if a given element is statically positioned
1554
- * @param element - raw DOM element
1555
- */
1556
- function isStaticPositioned(element) {
1557
- return (getStyle(element, "position") || 'static' ) === 'static';
1558
- }
1559
-
1560
- /**
1561
- * returns the closest, non-statically positioned parentOffset of a given element
1562
- * @param element
1563
- */
1564
- var parentOffsetEl = function (element) {
1565
- var docDomEl = $document[0];
1566
- var offsetParent = element.offsetParent || docDomEl;
1567
- while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
1568
- offsetParent = offsetParent.offsetParent;
1569
- }
1570
- return offsetParent || docDomEl;
1571
- };
1572
-
1573
- return {
1574
- /**
1575
- * Provides read-only equivalent of jQuery's position function:
1576
- * http://api.jquery.com/position/
1577
- */
1578
- position: function (element) {
1579
- var elBCR = this.offset(element);
1580
- var offsetParentBCR = { top: 0, left: 0 };
1581
- var offsetParentEl = parentOffsetEl(element[0]);
1582
- if (offsetParentEl != $document[0]) {
1583
- offsetParentBCR = this.offset(angular.element(offsetParentEl));
1584
- offsetParentBCR.top += offsetParentEl.clientTop;
1585
- offsetParentBCR.left += offsetParentEl.clientLeft;
1586
- }
1587
-
1588
- return {
1589
- width: element.prop('offsetWidth'),
1590
- height: element.prop('offsetHeight'),
1591
- top: elBCR.top - offsetParentBCR.top,
1592
- left: elBCR.left - offsetParentBCR.left
1593
- };
1594
- },
1595
-
1596
- /**
1597
- * Provides read-only equivalent of jQuery's offset function:
1598
- * http://api.jquery.com/offset/
1599
- */
1600
- offset: function (element) {
1601
- var boundingClientRect = element[0].getBoundingClientRect();
1602
- return {
1603
- width: element.prop('offsetWidth'),
1604
- height: element.prop('offsetHeight'),
1605
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
1606
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
1607
- };
1608
- },
1609
-
1610
- /**
1611
- * Provides the coordinates of the mouse
1612
- */
1613
- mouse: function () {
1614
- return {x: mouseX, y: mouseY};
1615
- }
1616
- };
1617
- }]);
1618
-
1619
- /**
1620
- * The following features are still outstanding: animation as a
1621
- * function, placement as a function, inside, support for more triggers than
1622
- * just mouse enter/leave, html tooltips, and selector delegation.
1623
- */
1624
- angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
1625
-
1626
- /**
1627
- * The $tooltip service creates tooltip- and popover-like directives as well as
1628
- * houses global options for them.
1629
- */
1630
- .provider( '$tooltip', function () {
1631
- // The default options tooltip and popover.
1632
- var defaultOptions = {
1633
- placement: 'top',
1634
- animation: true,
1635
- popupDelay: 0
1636
- };
1637
-
1638
- // Default hide triggers for each show trigger
1639
- var triggerMap = {
1640
- 'mouseenter': 'mouseleave',
1641
- 'click': 'click',
1642
- 'focus': 'blur'
1643
- };
1644
-
1645
- // The options specified to the provider globally.
1646
- var globalOptions = {};
1647
-
1648
- /**
1649
- * `options({})` allows global configuration of all tooltips in the
1650
- * application.
1651
- *
1652
- * var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
1653
- * // place tooltips left instead of top by default
1654
- * $tooltipProvider.options( { placement: 'left' } );
1655
- * });
1656
- */
1657
- this.options = function( value ) {
1658
- angular.extend( globalOptions, value );
1659
- };
1660
-
1661
- /**
1662
- * This allows you to extend the set of trigger mappings available. E.g.:
1663
- *
1664
- * $tooltipProvider.setTriggers( 'openTrigger': 'closeTrigger' );
1665
- */
1666
- this.setTriggers = function setTriggers ( triggers ) {
1667
- angular.extend( triggerMap, triggers );
1668
- };
1669
-
1670
- /**
1671
- * This is a helper function for translating camel-case to snake-case.
1672
- */
1673
- function snake_case(name){
1674
- var regexp = /[A-Z]/g;
1675
- var separator = '-';
1676
- return name.replace(regexp, function(letter, pos) {
1677
- return (pos ? separator : '') + letter.toLowerCase();
1678
- });
1679
- }
1680
-
1681
- /**
1682
- * Returns the actual instance of the $tooltip service.
1683
- * TODO support multiple triggers
1684
- */
1685
- this.$get = [ '$window', '$compile', '$timeout', '$parse', '$document', '$position', '$interpolate', function ( $window, $compile, $timeout, $parse, $document, $position, $interpolate ) {
1686
- return function $tooltip ( type, prefix, defaultTriggerShow ) {
1687
- var options = angular.extend( {}, defaultOptions, globalOptions );
1688
-
1689
- /**
1690
- * Returns an object of show and hide triggers.
1691
- *
1692
- * If a trigger is supplied,
1693
- * it is used to show the tooltip; otherwise, it will use the `trigger`
1694
- * option passed to the `$tooltipProvider.options` method; else it will
1695
- * default to the trigger supplied to this directive factory.
1696
- *
1697
- * The hide trigger is based on the show trigger. If the `trigger` option
1698
- * was passed to the `$tooltipProvider.options` method, it will use the
1699
- * mapped trigger from `triggerMap` or the passed trigger if the map is
1700
- * undefined; otherwise, it uses the `triggerMap` value of the show
1701
- * trigger; else it will just use the show trigger.
1702
- */
1703
- function setTriggers ( trigger ) {
1704
- var show, hide;
1705
-
1706
- show = trigger || options.trigger || defaultTriggerShow;
1707
- if ( angular.isDefined ( options.trigger ) ) {
1708
- hide = triggerMap[options.trigger] || show;
1709
- } else {
1710
- hide = triggerMap[show] || show;
1711
- }
1712
-
1713
- return {
1714
- show: show,
1715
- hide: hide
1716
- };
1717
- }
1718
-
1719
- var directiveName = snake_case( type );
1720
- var triggers = setTriggers( undefined );
1721
-
1722
- var startSym = $interpolate.startSymbol();
1723
- var endSym = $interpolate.endSymbol();
1724
- var template =
1725
- '<'+ directiveName +'-popup '+
1726
- 'title="'+startSym+'tt_title'+endSym+'" '+
1727
- 'content="'+startSym+'tt_content'+endSym+'" '+
1728
- 'placement="'+startSym+'tt_placement'+endSym+'" '+
1729
- 'animation="tt_animation()" '+
1730
- 'is-open="tt_isOpen"'+
1731
- '>'+
1732
- '</'+ directiveName +'-popup>';
1733
-
1734
- return {
1735
- restrict: 'EA',
1736
- scope: true,
1737
- link: function link ( scope, element, attrs ) {
1738
- var tooltip = $compile( template )( scope );
1739
- var transitionTimeout;
1740
- var popupTimeout;
1741
- var $body;
1742
- var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
1743
-
1744
- // By default, the tooltip is not open.
1745
- // TODO add ability to start tooltip opened
1746
- scope.tt_isOpen = false;
1747
-
1748
- function toggleTooltipBind () {
1749
- if ( ! scope.tt_isOpen ) {
1750
- showTooltipBind();
1751
- } else {
1752
- hideTooltipBind();
1753
- }
1754
- }
1755
-
1756
- // Show the tooltip with delay if specified, otherwise show it immediately
1757
- function showTooltipBind() {
1758
- if ( scope.tt_popupDelay ) {
1759
- popupTimeout = $timeout( show, scope.tt_popupDelay );
1760
- } else {
1761
- scope.$apply( show );
1762
- }
1763
- }
1764
-
1765
- function hideTooltipBind () {
1766
- scope.$apply(function () {
1767
- hide();
1768
- });
1769
- }
1770
-
1771
- // Show the tooltip popup element.
1772
- function show() {
1773
- var position,
1774
- ttWidth,
1775
- ttHeight,
1776
- ttPosition;
1777
-
1778
- // Don't show empty tooltips.
1779
- if ( ! scope.tt_content ) {
1780
- return;
1781
- }
1782
-
1783
- // If there is a pending remove transition, we must cancel it, lest the
1784
- // tooltip be mysteriously removed.
1785
- if ( transitionTimeout ) {
1786
- $timeout.cancel( transitionTimeout );
1787
- }
1788
-
1789
- // Set the initial positioning.
1790
- tooltip.css({ top: 0, left: 0, display: 'block' });
1791
-
1792
- // Now we add it to the DOM because need some info about it. But it's not
1793
- // visible yet anyway.
1794
- if ( appendToBody ) {
1795
- $body = $body || $document.find( 'body' );
1796
- $body.append( tooltip );
1797
- } else {
1798
- element.after( tooltip );
1799
- }
1800
-
1801
- // Get the position of the directive element.
1802
- position = options.appendToBody ? $position.offset( element ) : $position.position( element );
1803
-
1804
- // Get the height and width of the tooltip so we can center it.
1805
- ttWidth = tooltip.prop( 'offsetWidth' );
1806
- ttHeight = tooltip.prop( 'offsetHeight' );
1807
-
1808
- // Calculate the tooltip's top and left coordinates to center it with
1809
- // this directive.
1810
- switch ( scope.tt_placement ) {
1811
- case 'mouse':
1812
- var mousePos = $position.mouse();
1813
- ttPosition = {
1814
- top: mousePos.y,
1815
- left: mousePos.x
1816
- };
1817
- break;
1818
- case 'right':
1819
- ttPosition = {
1820
- top: position.top + position.height / 2 - ttHeight / 2,
1821
- left: position.left + position.width
1822
- };
1823
- break;
1824
- case 'bottom':
1825
- ttPosition = {
1826
- top: position.top + position.height,
1827
- left: position.left + position.width / 2 - ttWidth / 2
1828
- };
1829
- break;
1830
- case 'left':
1831
- ttPosition = {
1832
- top: position.top + position.height / 2 - ttHeight / 2,
1833
- left: position.left - ttWidth
1834
- };
1835
- break;
1836
- default:
1837
- ttPosition = {
1838
- top: position.top - ttHeight,
1839
- left: position.left + position.width / 2 - ttWidth / 2
1840
- };
1841
- break;
1842
- }
1843
-
1844
- ttPosition.top += 'px';
1845
- ttPosition.left += 'px';
1846
-
1847
- // Now set the calculated positioning.
1848
- tooltip.css( ttPosition );
1849
-
1850
- // And show the tooltip.
1851
- scope.tt_isOpen = true;
1852
- }
1853
-
1854
- // Hide the tooltip popup element.
1855
- function hide() {
1856
- // First things first: we don't show it anymore.
1857
- scope.tt_isOpen = false;
1858
-
1859
- //if tooltip is going to be shown after delay, we must cancel this
1860
- $timeout.cancel( popupTimeout );
1861
-
1862
- // And now we remove it from the DOM. However, if we have animation, we
1863
- // need to wait for it to expire beforehand.
1864
- // FIXME: this is a placeholder for a port of the transitions library.
1865
- if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) {
1866
- transitionTimeout = $timeout( function () { tooltip.remove(); }, 500 );
1867
- } else {
1868
- tooltip.remove();
1869
- }
1870
- }
1871
-
1872
- /**
1873
- * Observe the relevant attributes.
1874
- */
1875
- attrs.$observe( type, function ( val ) {
1876
- scope.tt_content = val;
1877
- });
1878
-
1879
- attrs.$observe( prefix+'Title', function ( val ) {
1880
- scope.tt_title = val;
1881
- });
1882
-
1883
- attrs.$observe( prefix+'Placement', function ( val ) {
1884
- scope.tt_placement = angular.isDefined( val ) ? val : options.placement;
1885
- });
1886
-
1887
- attrs.$observe( prefix+'Animation', function ( val ) {
1888
- scope.tt_animation = angular.isDefined( val ) ? $parse( val ) : function(){ return options.animation; };
1889
- });
1890
-
1891
- attrs.$observe( prefix+'PopupDelay', function ( val ) {
1892
- var delay = parseInt( val, 10 );
1893
- scope.tt_popupDelay = ! isNaN(delay) ? delay : options.popupDelay;
1894
- });
1895
-
1896
- attrs.$observe( prefix+'Trigger', function ( val ) {
1897
- element.unbind( triggers.show );
1898
- element.unbind( triggers.hide );
1899
-
1900
- triggers = setTriggers( val );
1901
-
1902
- if ( triggers.show === triggers.hide ) {
1903
- element.bind( triggers.show, toggleTooltipBind );
1904
- } else {
1905
- element.bind( triggers.show, showTooltipBind );
1906
- element.bind( triggers.hide, hideTooltipBind );
1907
- }
1908
- });
1909
-
1910
- attrs.$observe( prefix+'AppendToBody', function ( val ) {
1911
- appendToBody = angular.isDefined( val ) ? $parse( val )( scope ) : appendToBody;
1912
- });
1913
-
1914
- // if a tooltip is attached to <body> we need to remove it on
1915
- // location change as its parent scope will probably not be destroyed
1916
- // by the change.
1917
- if ( appendToBody ) {
1918
- scope.$on('$locationChangeSuccess', function closeTooltipOnLocationChangeSuccess () {
1919
- if ( scope.tt_isOpen ) {
1920
- hide();
1921
- }
1922
- });
1923
- }
1924
-
1925
- // Make sure tooltip is destroyed and removed.
1926
- scope.$on('$destroy', function onDestroyTooltip() {
1927
- if ( scope.tt_isOpen ) {
1928
- hide();
1929
- } else {
1930
- tooltip.remove();
1931
- }
1932
- });
1933
- }
1934
- };
1935
- };
1936
- }];
1937
- })
1938
-
1939
- .directive( 'tooltipPopup', function () {
1940
- return {
1941
- restrict: 'E',
1942
- replace: true,
1943
- scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
1944
- templateUrl: 'template/tooltip/tooltip-popup.html'
1945
- };
1946
- })
1947
-
1948
- .directive( 'tooltip', [ '$tooltip', function ( $tooltip ) {
1949
- return $tooltip( 'tooltip', 'tooltip', 'mouseenter' );
1950
- }])
1951
-
1952
- .directive( 'tooltipHtmlUnsafePopup', function () {
1953
- return {
1954
- restrict: 'E',
1955
- replace: true,
1956
- scope: { content: '@', placement: '@', animation: '&', isOpen: '&' },
1957
- templateUrl: 'template/tooltip/tooltip-html-unsafe-popup.html'
1958
- };
1959
- })
1960
-
1961
- .directive( 'tooltipHtmlUnsafe', [ '$tooltip', function ( $tooltip ) {
1962
- return $tooltip( 'tooltipHtmlUnsafe', 'tooltip', 'mouseenter' );
1963
- }]);
1964
-
1965
- /**
1966
- * The following features are still outstanding: popup delay, animation as a
1967
- * function, placement as a function, inside, support for more triggers than
1968
- * just mouse enter/leave, html popovers, and selector delegatation.
1969
- */
1970
- angular.module( 'ui.bootstrap.popover', [ 'ui.bootstrap.tooltip' ] )
1971
- .directive( 'popoverPopup', function () {
1972
- return {
1973
- restrict: 'EA',
1974
- replace: true,
1975
- scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' },
1976
- templateUrl: 'template/popover/popover.html'
1977
- };
1978
- })
1979
- .directive( 'popover', [ '$compile', '$timeout', '$parse', '$window', '$tooltip', function ( $compile, $timeout, $parse, $window, $tooltip ) {
1980
- return $tooltip( 'popover', 'popover', 'click' );
1981
- }]);
1982
-
1983
-
1984
- angular.module('ui.bootstrap.progressbar', ['ui.bootstrap.transition'])
1985
-
1986
- .constant('progressConfig', {
1987
- animate: true,
1988
- autoType: false,
1989
- stackedTypes: ['success', 'info', 'warning', 'danger']
1990
- })
1991
-
1992
- .controller('ProgressBarController', ['$scope', '$attrs', 'progressConfig', function($scope, $attrs, progressConfig) {
1993
-
1994
- // Whether bar transitions should be animated
1995
- var animate = angular.isDefined($attrs.animate) ? $scope.$eval($attrs.animate) : progressConfig.animate;
1996
- var autoType = angular.isDefined($attrs.autoType) ? $scope.$eval($attrs.autoType) : progressConfig.autoType;
1997
- var stackedTypes = angular.isDefined($attrs.stackedTypes) ? $scope.$eval('[' + $attrs.stackedTypes + ']') : progressConfig.stackedTypes;
1998
-
1999
- // Create bar object
2000
- this.makeBar = function(newBar, oldBar, index) {
2001
- var newValue = (angular.isObject(newBar)) ? newBar.value : (newBar || 0);
2002
- var oldValue = (angular.isObject(oldBar)) ? oldBar.value : (oldBar || 0);
2003
- var type = (angular.isObject(newBar) && angular.isDefined(newBar.type)) ? newBar.type : (autoType) ? getStackedType(index || 0) : null;
2004
-
2005
- return {
2006
- from: oldValue,
2007
- to: newValue,
2008
- type: type,
2009
- animate: animate
2010
- };
2011
- };
2012
-
2013
- function getStackedType(index) {
2014
- return stackedTypes[index];
2015
- }
2016
-
2017
- this.addBar = function(bar) {
2018
- $scope.bars.push(bar);
2019
- $scope.totalPercent += bar.to;
2020
- };
2021
-
2022
- this.clearBars = function() {
2023
- $scope.bars = [];
2024
- $scope.totalPercent = 0;
2025
- };
2026
- this.clearBars();
2027
- }])
2028
-
2029
- .directive('progress', function() {
2030
- return {
2031
- restrict: 'EA',
2032
- replace: true,
2033
- controller: 'ProgressBarController',
2034
- scope: {
2035
- value: '=percent',
2036
- onFull: '&',
2037
- onEmpty: '&'
2038
- },
2039
- templateUrl: 'template/progressbar/progress.html',
2040
- link: function(scope, element, attrs, controller) {
2041
- scope.$watch('value', function(newValue, oldValue) {
2042
- controller.clearBars();
2043
-
2044
- if (angular.isArray(newValue)) {
2045
- // Stacked progress bar
2046
- for (var i=0, n=newValue.length; i < n; i++) {
2047
- controller.addBar(controller.makeBar(newValue[i], oldValue[i], i));
2048
- }
2049
- } else {
2050
- // Simple bar
2051
- controller.addBar(controller.makeBar(newValue, oldValue));
2052
- }
2053
- }, true);
2054
-
2055
- // Total percent listeners
2056
- scope.$watch('totalPercent', function(value) {
2057
- if (value >= 100) {
2058
- scope.onFull();
2059
- } else if (value <= 0) {
2060
- scope.onEmpty();
2061
- }
2062
- }, true);
2063
- }
2064
- };
2065
- })
2066
-
2067
- .directive('progressbar', ['$transition', function($transition) {
2068
- return {
2069
- restrict: 'EA',
2070
- replace: true,
2071
- scope: {
2072
- width: '=',
2073
- old: '=',
2074
- type: '=',
2075
- animate: '='
2076
- },
2077
- templateUrl: 'template/progressbar/bar.html',
2078
- link: function(scope, element) {
2079
- scope.$watch('width', function(value) {
2080
- if (scope.animate) {
2081
- element.css('width', scope.old + '%');
2082
- $transition(element, {width: value + '%'});
2083
- } else {
2084
- element.css('width', value + '%');
2085
- }
2086
- });
2087
- }
2088
- };
2089
- }]);
2090
- angular.module('ui.bootstrap.rating', [])
2091
-
2092
- .constant('ratingConfig', {
2093
- max: 5
2094
- })
2095
-
2096
- .directive('rating', ['ratingConfig', '$parse', function(ratingConfig, $parse) {
2097
- return {
2098
- restrict: 'EA',
2099
- scope: {
2100
- value: '='
2101
- },
2102
- templateUrl: 'template/rating/rating.html',
2103
- replace: true,
2104
- link: function(scope, element, attrs) {
2105
-
2106
- var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max;
2107
-
2108
- scope.range = [];
2109
- for (var i = 1; i <= maxRange; i++) {
2110
- scope.range.push(i);
2111
- }
2112
-
2113
- scope.rate = function(value) {
2114
- if ( ! scope.readonly ) {
2115
- scope.value = value;
2116
- }
2117
- };
2118
-
2119
- scope.enter = function(value) {
2120
- if ( ! scope.readonly ) {
2121
- scope.val = value;
2122
- }
2123
- };
2124
-
2125
- scope.reset = function() {
2126
- scope.val = angular.copy(scope.value);
2127
- };
2128
- scope.reset();
2129
-
2130
- scope.$watch('value', function(value) {
2131
- scope.val = value;
2132
- });
2133
-
2134
- scope.readonly = false;
2135
- if (attrs.readonly) {
2136
- scope.$parent.$watch($parse(attrs.readonly), function(value) {
2137
- scope.readonly = !!value;
2138
- });
2139
- }
2140
- }
2141
- };
2142
- }]);
2143
-
2144
- /**
2145
- * @ngdoc overview
2146
- * @name ui.bootstrap.tabs
2147
- *
2148
- * @description
2149
- * AngularJS version of the tabs directive.
2150
- */
2151
-
2152
- angular.module('ui.bootstrap.tabs', [])
2153
-
2154
- .directive('tabs', function() {
2155
- return function() {
2156
- throw new Error("The `tabs` directive is deprecated, please migrate to `tabset`. Instructions can be found at http://github.com/angular-ui/bootstrap/tree/master/CHANGELOG.md");
2157
- };
2158
- })
2159
-
2160
- .controller('TabsetController', ['$scope', '$element',
2161
- function TabsetCtrl($scope, $element) {
2162
- var ctrl = this,
2163
- tabs = ctrl.tabs = $scope.tabs = [];
2164
-
2165
- ctrl.select = function(tab) {
2166
- angular.forEach(tabs, function(tab) {
2167
- tab.active = false;
2168
- });
2169
- tab.active = true;
2170
- };
2171
-
2172
- ctrl.addTab = function addTab(tab) {
2173
- tabs.push(tab);
2174
- if (tabs.length == 1) {
2175
- ctrl.select(tab);
2176
- }
2177
- };
2178
-
2179
- ctrl.removeTab = function removeTab(tab) {
2180
- var index = tabs.indexOf(tab);
2181
- //Select a new tab if the tab to be removed is selected
2182
- if (tab.active && tabs.length > 1) {
2183
- //If this is the last tab, select the previous tab. else, the next tab.
2184
- var newActiveIndex = index == tabs.length - 1 ? index - 1 : index + 1;
2185
- ctrl.select(tabs[newActiveIndex]);
2186
- }
2187
- tabs.splice(index, 1);
2188
- };
2189
- }])
2190
-
2191
- /**
2192
- * @ngdoc directive
2193
- * @name ui.bootstrap.tabs.directive:tabset
2194
- * @restrict EA
2195
- *
2196
- * @description
2197
- * Tabset is the outer container for the tabs directive
2198
- *
2199
- * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2200
- *
2201
- * @example
2202
- <example module="ui.bootstrap">
2203
- <file name="index.html">
2204
- <tabset>
2205
- <tab heading="Vertical Tab 1"><b>First</b> Content!</tab>
2206
- <tab heading="Vertical Tab 2"><i>Second</i> Content!</tab>
2207
- </tabset>
2208
- <hr />
2209
- <tabset vertical="true">
2210
- <tab heading="Vertical Tab 1"><b>First</b> Vertical Content!</tab>
2211
- <tab heading="Vertical Tab 2"><i>Second</i> Vertical Content!</tab>
2212
- </tabset>
2213
- </file>
2214
- </example>
2215
- */
2216
- .directive('tabset', function() {
2217
- return {
2218
- restrict: 'EA',
2219
- transclude: true,
2220
- scope: {},
2221
- controller: 'TabsetController',
2222
- templateUrl: 'template/tabs/tabset.html',
2223
- link: function(scope, element, attrs) {
2224
- scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
2225
- scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2226
- }
2227
- };
2228
- })
2229
-
2230
- /**
2231
- * @ngdoc directive
2232
- * @name ui.bootstrap.tabs.directive:tab
2233
- * @restrict EA
2234
- *
2235
- * @param {string=} heading The visible heading, or title, of the tab. Set HTML headings with {@link ui.bootstrap.tabs.directive:tabHeading tabHeading}.
2236
- * @param {string=} select An expression to evaluate when the tab is selected.
2237
- * @param {boolean=} active A binding, telling whether or not this tab is selected.
2238
- * @param {boolean=} disabled A binding, telling whether or not this tab is disabled.
2239
- *
2240
- * @description
2241
- * Creates a tab with a heading and content. Must be placed within a {@link ui.bootstrap.tabs.directive:tabset tabset}.
2242
- *
2243
- * @example
2244
- <example module="ui.bootstrap">
2245
- <file name="index.html">
2246
- <div ng-controller="TabsDemoCtrl">
2247
- <button class="btn btn-small" ng-click="items[0].active = true">
2248
- Select item 1, using active binding
2249
- </button>
2250
- <button class="btn btn-small" ng-click="items[1].disabled = !items[1].disabled">
2251
- Enable/disable item 2, using disabled binding
2252
- </button>
2253
- <br />
2254
- <tabset>
2255
- <tab heading="Tab 1">First Tab</tab>
2256
- <tab select="alertMe()">
2257
- <tab-heading><i class="icon-bell"></i> Alert me!</tab-heading>
2258
- Second Tab, with alert callback and html heading!
2259
- </tab>
2260
- <tab ng-repeat="item in items"
2261
- heading="{{item.title}}"
2262
- disabled="item.disabled"
2263
- active="item.active">
2264
- {{item.content}}
2265
- </tab>
2266
- </tabset>
2267
- </div>
2268
- </file>
2269
- <file name="script.js">
2270
- function TabsDemoCtrl($scope) {
2271
- $scope.items = [
2272
- { title:"Dynamic Title 1", content:"Dynamic Item 0" },
2273
- { title:"Dynamic Title 2", content:"Dynamic Item 1", disabled: true }
2274
- ];
2275
-
2276
- $scope.alertMe = function() {
2277
- setTimeout(function() {
2278
- alert("You've selected the alert tab!");
2279
- });
2280
- };
2281
- };
2282
- </file>
2283
- </example>
2284
- */
2285
-
2286
- /**
2287
- * @ngdoc directive
2288
- * @name ui.bootstrap.tabs.directive:tabHeading
2289
- * @restrict EA
2290
- *
2291
- * @description
2292
- * Creates an HTML heading for a {@link ui.bootstrap.tabs.directive:tab tab}. Must be placed as a child of a tab element.
2293
- *
2294
- * @example
2295
- <example module="ui.bootstrap">
2296
- <file name="index.html">
2297
- <tabset>
2298
- <tab>
2299
- <tab-heading><b>HTML</b> in my titles?!</tab-heading>
2300
- And some content, too!
2301
- </tab>
2302
- <tab>
2303
- <tab-heading><i class="icon-heart"></i> Icon heading?!?</tab-heading>
2304
- That's right.
2305
- </tab>
2306
- </tabset>
2307
- </file>
2308
- </example>
2309
- */
2310
- .directive('tab', ['$parse', '$http', '$templateCache', '$compile',
2311
- function($parse, $http, $templateCache, $compile) {
2312
- return {
2313
- require: '^tabset',
2314
- restrict: 'EA',
2315
- replace: true,
2316
- templateUrl: 'template/tabs/tab.html',
2317
- transclude: true,
2318
- scope: {
2319
- heading: '@',
2320
- onSelect: '&select' //This callback is called in contentHeadingTransclude
2321
- //once it inserts the tab's content into the dom
2322
- },
2323
- controller: function() {
2324
- //Empty controller so other directives can require being 'under' a tab
2325
- },
2326
- compile: function(elm, attrs, transclude) {
2327
- return function postLink(scope, elm, attrs, tabsetCtrl) {
2328
- var getActive, setActive;
2329
- scope.active = false; // default value
2330
- if (attrs.active) {
2331
- getActive = $parse(attrs.active);
2332
- setActive = getActive.assign;
2333
- scope.$parent.$watch(getActive, function updateActive(value) {
2334
- if ( !!value && scope.disabled ) {
2335
- setActive(scope.$parent, false); // Prevent active assignment
2336
- } else {
2337
- scope.active = !!value;
2338
- }
2339
- });
2340
- } else {
2341
- setActive = getActive = angular.noop;
2342
- }
2343
-
2344
- scope.$watch('active', function(active) {
2345
- setActive(scope.$parent, active);
2346
- if (active) {
2347
- tabsetCtrl.select(scope);
2348
- scope.onSelect();
2349
- }
2350
- });
2351
-
2352
- scope.disabled = false;
2353
- if ( attrs.disabled ) {
2354
- scope.$parent.$watch($parse(attrs.disabled), function(value) {
2355
- scope.disabled = !! value;
2356
- });
2357
- }
2358
-
2359
- scope.select = function() {
2360
- if ( ! scope.disabled ) {
2361
- scope.active = true;
2362
- }
2363
- };
2364
-
2365
- tabsetCtrl.addTab(scope);
2366
- scope.$on('$destroy', function() {
2367
- tabsetCtrl.removeTab(scope);
2368
- });
2369
- //If the tabset sets this tab to active, set the parent scope's active
2370
- //binding too. We do this so the watch for the parent's initial active
2371
- //value won't overwrite what is initially set by the tabset
2372
- if (scope.active) {
2373
- setActive(scope.$parent, true);
2374
- }
2375
-
2376
- //Transclude the collection of sibling elements. Use forEach to find
2377
- //the heading if it exists. We don't use a directive for tab-heading
2378
- //because it is problematic. Discussion @ http://git.io/MSNPwQ
2379
- transclude(scope.$parent, function(clone) {
2380
- //Look at every element in the clone collection. If it's tab-heading,
2381
- //mark it as that. If it's not tab-heading, mark it as tab contents
2382
- var contents = [], heading;
2383
- angular.forEach(clone, function(el) {
2384
- //See if it's a tab-heading attr or element directive
2385
- //First make sure it's a normal element, one that has a tagName
2386
- if (el.tagName &&
2387
- (el.hasAttribute("tab-heading") ||
2388
- el.hasAttribute("data-tab-heading") ||
2389
- el.tagName.toLowerCase() == "tab-heading" ||
2390
- el.tagName.toLowerCase() == "data-tab-heading"
2391
- )) {
2392
- heading = el;
2393
- } else {
2394
- contents.push(el);
2395
- }
2396
- });
2397
- //Share what we found on the scope, so our tabHeadingTransclude and
2398
- //tabContentTransclude directives can find out what the heading and
2399
- //contents are.
2400
- if (heading) {
2401
- scope.headingElement = angular.element(heading);
2402
- }
2403
- scope.contentElement = angular.element(contents);
2404
- });
2405
- };
2406
- }
2407
- };
2408
- }])
2409
-
2410
- .directive('tabHeadingTransclude', [function() {
2411
- return {
2412
- restrict: 'A',
2413
- require: '^tab',
2414
- link: function(scope, elm, attrs, tabCtrl) {
2415
- scope.$watch('headingElement', function updateHeadingElement(heading) {
2416
- if (heading) {
2417
- elm.html('');
2418
- elm.append(heading);
2419
- }
2420
- });
2421
- }
2422
- };
2423
- }])
2424
-
2425
- .directive('tabContentTransclude', ['$parse', function($parse) {
2426
- return {
2427
- restrict: 'A',
2428
- require: '^tabset',
2429
- link: function(scope, elm, attrs, tabsetCtrl) {
2430
- scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
2431
- elm.html('');
2432
- if (tab) {
2433
- elm.append(tab.contentElement);
2434
- }
2435
- });
2436
- }
2437
- };
2438
- }])
2439
-
2440
- ;
2441
-
2442
-
2443
- angular.module('ui.bootstrap.timepicker', [])
2444
-
2445
- .filter('pad', function() {
2446
- return function(input) {
2447
- if ( angular.isDefined(input) && input.toString().length < 2 ) {
2448
- input = '0' + input;
2449
- }
2450
- return input;
2451
- };
2452
- })
2453
-
2454
- .constant('timepickerConfig', {
2455
- hourStep: 1,
2456
- minuteStep: 1,
2457
- showMeridian: true,
2458
- meridians: ['AM', 'PM'],
2459
- readonlyInput: false,
2460
- mousewheel: true
2461
- })
2462
-
2463
- .directive('timepicker', ['padFilter', '$parse', 'timepickerConfig', function (padFilter, $parse, timepickerConfig) {
2464
- return {
2465
- restrict: 'EA',
2466
- require:'ngModel',
2467
- replace: true,
2468
- templateUrl: 'template/timepicker/timepicker.html',
2469
- scope: {
2470
- model: '=ngModel'
2471
- },
2472
- link: function(scope, element, attrs, ngModelCtrl) {
2473
- var selected = new Date(), meridians = timepickerConfig.meridians;
2474
-
2475
- var hourStep = timepickerConfig.hourStep;
2476
- if (attrs.hourStep) {
2477
- scope.$parent.$watch($parse(attrs.hourStep), function(value) {
2478
- hourStep = parseInt(value, 10);
2479
- });
2480
- }
2481
-
2482
- var minuteStep = timepickerConfig.minuteStep;
2483
- if (attrs.minuteStep) {
2484
- scope.$parent.$watch($parse(attrs.minuteStep), function(value) {
2485
- minuteStep = parseInt(value, 10);
2486
- });
2487
- }
2488
-
2489
- // 12H / 24H mode
2490
- scope.showMeridian = timepickerConfig.showMeridian;
2491
- if (attrs.showMeridian) {
2492
- scope.$parent.$watch($parse(attrs.showMeridian), function(value) {
2493
- scope.showMeridian = !! value;
2494
-
2495
- if ( ! scope.model ) {
2496
- // Reset
2497
- var dt = new Date( selected );
2498
- var hours = getScopeHours();
2499
- if (angular.isDefined( hours )) {
2500
- dt.setHours( hours );
2501
- }
2502
- scope.model = new Date( dt );
2503
- } else {
2504
- refreshTemplate();
2505
- }
2506
- });
2507
- }
2508
-
2509
- // Get scope.hours in 24H mode if valid
2510
- function getScopeHours ( ) {
2511
- var hours = parseInt( scope.hours, 10 );
2512
- var valid = ( scope.showMeridian ) ? (hours > 0 && hours < 13) : (hours >= 0 && hours < 24);
2513
- if ( !valid ) {
2514
- return;
2515
- }
2516
-
2517
- if ( scope.showMeridian ) {
2518
- if ( hours === 12 ) {
2519
- hours = 0;
2520
- }
2521
- if ( scope.meridian === meridians[1] ) {
2522
- hours = hours + 12;
2523
- }
2524
- }
2525
- return hours;
2526
- }
2527
-
2528
- // Input elements
2529
- var inputs = element.find('input');
2530
- var hoursInputEl = inputs.eq(0), minutesInputEl = inputs.eq(1);
2531
-
2532
- // Respond on mousewheel spin
2533
- var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
2534
- if ( mousewheel ) {
2535
-
2536
- var isScrollingUp = function(e) {
2537
- if (e.originalEvent) {
2538
- e = e.originalEvent;
2539
- }
2540
- return (e.detail || e.wheelDelta > 0);
2541
- };
2542
-
2543
- hoursInputEl.bind('mousewheel', function(e) {
2544
- scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
2545
- e.preventDefault();
2546
- });
2547
-
2548
- minutesInputEl.bind('mousewheel', function(e) {
2549
- scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
2550
- e.preventDefault();
2551
- });
2552
- }
2553
-
2554
- var keyboardChange = false;
2555
- scope.readonlyInput = (angular.isDefined(attrs.readonlyInput)) ? scope.$eval(attrs.readonlyInput) : timepickerConfig.readonlyInput;
2556
- if ( ! scope.readonlyInput ) {
2557
- scope.updateHours = function() {
2558
- var hours = getScopeHours();
2559
-
2560
- if ( angular.isDefined(hours) ) {
2561
- keyboardChange = 'h';
2562
- if ( scope.model === null ) {
2563
- scope.model = new Date( selected );
2564
- }
2565
- scope.model.setHours( hours );
2566
- } else {
2567
- scope.model = null;
2568
- scope.validHours = false;
2569
- }
2570
- };
2571
-
2572
- hoursInputEl.bind('blur', function(e) {
2573
- if ( scope.validHours && scope.hours < 10) {
2574
- scope.$apply( function() {
2575
- scope.hours = padFilter( scope.hours );
2576
- });
2577
- }
2578
- });
2579
-
2580
- scope.updateMinutes = function() {
2581
- var minutes = parseInt(scope.minutes, 10);
2582
- if ( minutes >= 0 && minutes < 60 ) {
2583
- keyboardChange = 'm';
2584
- if ( scope.model === null ) {
2585
- scope.model = new Date( selected );
2586
- }
2587
- scope.model.setMinutes( minutes );
2588
- } else {
2589
- scope.model = null;
2590
- scope.validMinutes = false;
2591
- }
2592
- };
2593
-
2594
- minutesInputEl.bind('blur', function(e) {
2595
- if ( scope.validMinutes && scope.minutes < 10 ) {
2596
- scope.$apply( function() {
2597
- scope.minutes = padFilter( scope.minutes );
2598
- });
2599
- }
2600
- });
2601
- } else {
2602
- scope.updateHours = angular.noop;
2603
- scope.updateMinutes = angular.noop;
2604
- }
2605
-
2606
- scope.$watch( function getModelTimestamp() {
2607
- return +scope.model;
2608
- }, function( timestamp ) {
2609
- if ( !isNaN( timestamp ) && timestamp > 0 ) {
2610
- selected = new Date( timestamp );
2611
- refreshTemplate();
2612
- }
2613
- });
2614
-
2615
- function refreshTemplate() {
2616
- var hours = selected.getHours();
2617
- if ( scope.showMeridian ) {
2618
- // Convert 24 to 12 hour system
2619
- hours = ( hours === 0 || hours === 12 ) ? 12 : hours % 12;
2620
- }
2621
- scope.hours = ( keyboardChange === 'h' ) ? hours : padFilter(hours);
2622
- scope.validHours = true;
2623
-
2624
- var minutes = selected.getMinutes();
2625
- scope.minutes = ( keyboardChange === 'm' ) ? minutes : padFilter(minutes);
2626
- scope.validMinutes = true;
2627
-
2628
- scope.meridian = ( scope.showMeridian ) ? (( selected.getHours() < 12 ) ? meridians[0] : meridians[1]) : '';
2629
-
2630
- keyboardChange = false;
2631
- }
2632
-
2633
- function addMinutes( minutes ) {
2634
- var dt = new Date( selected.getTime() + minutes * 60000 );
2635
- if ( dt.getDate() !== selected.getDate()) {
2636
- dt.setDate( dt.getDate() - 1 );
2637
- }
2638
- selected.setTime( dt.getTime() );
2639
- scope.model = new Date( selected );
2640
- }
2641
-
2642
- scope.incrementHours = function() {
2643
- addMinutes( hourStep * 60 );
2644
- };
2645
- scope.decrementHours = function() {
2646
- addMinutes( - hourStep * 60 );
2647
- };
2648
- scope.incrementMinutes = function() {
2649
- addMinutes( minuteStep );
2650
- };
2651
- scope.decrementMinutes = function() {
2652
- addMinutes( - minuteStep );
2653
- };
2654
- scope.toggleMeridian = function() {
2655
- addMinutes( 12 * 60 * (( selected.getHours() < 12 ) ? 1 : -1) );
2656
- };
2657
- }
2658
- };
2659
- }]);
2660
- angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
2661
-
2662
- /**
2663
- * A helper service that can parse typeahead's syntax (string provided by users)
2664
- * Extracted to a separate service for ease of unit testing
2665
- */
2666
- .factory('typeaheadParser', ['$parse', function ($parse) {
2667
-
2668
- // 00000111000000000000022200000000000000003333333333333330000000000044000
2669
- var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;
2670
-
2671
- return {
2672
- parse:function (input) {
2673
-
2674
- var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
2675
- if (!match) {
2676
- throw new Error(
2677
- "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
2678
- " but got '" + input + "'.");
2679
- }
2680
-
2681
- return {
2682
- itemName:match[3],
2683
- source:$parse(match[4]),
2684
- viewMapper:$parse(match[2] || match[1]),
2685
- modelMapper:$parse(match[1])
2686
- };
2687
- }
2688
- };
2689
- }])
2690
-
2691
- .directive('typeahead', ['$compile', '$parse', '$q', '$timeout', '$document', '$position', 'typeaheadParser', function ($compile, $parse, $q, $timeout, $document, $position, typeaheadParser) {
2692
-
2693
- var HOT_KEYS = [9, 13, 27, 38, 40];
2694
-
2695
- return {
2696
- require:'ngModel',
2697
- link:function (originalScope, element, attrs, modelCtrl) {
2698
-
2699
- var selected;
2700
-
2701
- //minimal no of characters that needs to be entered before typeahead kicks-in
2702
- var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
2703
-
2704
- //minimal wait time after last character typed before typehead kicks-in
2705
- var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
2706
-
2707
- //expressions used by typeahead
2708
- var parserResult = typeaheadParser.parse(attrs.typeahead);
2709
-
2710
- //should it restrict model values to the ones selected from the popup only?
2711
- var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
2712
-
2713
- var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
2714
-
2715
- var onSelectCallback = $parse(attrs.typeaheadOnSelect);
2716
-
2717
- //pop-up element used to display matches
2718
- var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
2719
- popUpEl.attr({
2720
- matches: 'matches',
2721
- active: 'activeIdx',
2722
- select: 'select(activeIdx)',
2723
- query: 'query',
2724
- position: 'position'
2725
- });
2726
-
2727
- //create a child scope for the typeahead directive so we are not polluting original scope
2728
- //with typeahead-specific data (matches, query etc.)
2729
- var scope = originalScope.$new();
2730
- originalScope.$on('$destroy', function(){
2731
- scope.$destroy();
2732
- });
2733
-
2734
- var resetMatches = function() {
2735
- scope.matches = [];
2736
- scope.activeIdx = -1;
2737
- };
2738
-
2739
- var getMatchesAsync = function(inputValue) {
2740
-
2741
- var locals = {$viewValue: inputValue};
2742
- isLoadingSetter(originalScope, true);
2743
- $q.when(parserResult.source(scope, locals)).then(function(matches) {
2744
-
2745
- //it might happen that several async queries were in progress if a user were typing fast
2746
- //but we are interested only in responses that correspond to the current view value
2747
- if (inputValue === modelCtrl.$viewValue) {
2748
- if (matches.length > 0) {
2749
-
2750
- scope.activeIdx = 0;
2751
- scope.matches.length = 0;
2752
-
2753
- //transform labels
2754
- for(var i=0; i<matches.length; i++) {
2755
- locals[parserResult.itemName] = matches[i];
2756
- scope.matches.push({
2757
- label: parserResult.viewMapper(scope, locals),
2758
- model: matches[i]
2759
- });
2760
- }
2761
-
2762
- scope.query = inputValue;
2763
- //position pop-up with matches - we need to re-calculate its position each time we are opening a window
2764
- //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
2765
- //due to other elements being rendered
2766
- scope.position = $position.position(element);
2767
- scope.position.top = scope.position.top + element.prop('offsetHeight');
2768
-
2769
- } else {
2770
- resetMatches();
2771
- }
2772
- isLoadingSetter(originalScope, false);
2773
- }
2774
- }, function(){
2775
- resetMatches();
2776
- isLoadingSetter(originalScope, false);
2777
- });
2778
- };
2779
-
2780
- resetMatches();
2781
-
2782
- //we need to propagate user's query so we can higlight matches
2783
- scope.query = undefined;
2784
-
2785
- //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
2786
- //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
2787
- modelCtrl.$parsers.push(function (inputValue) {
2788
-
2789
- var timeoutId;
2790
-
2791
- resetMatches();
2792
- if (selected) {
2793
- return inputValue;
2794
- } else {
2795
- if (inputValue && inputValue.length >= minSearch) {
2796
- if (waitTime > 0) {
2797
- if (timeoutId) {
2798
- $timeout.cancel(timeoutId);//cancel previous timeout
2799
- }
2800
- timeoutId = $timeout(function () {
2801
- getMatchesAsync(inputValue);
2802
- }, waitTime);
2803
- } else {
2804
- getMatchesAsync(inputValue);
2805
- }
2806
- }
2807
- }
2808
-
2809
- return isEditable ? inputValue : undefined;
2810
- });
2811
-
2812
- modelCtrl.$render = function () {
2813
- var locals = {};
2814
- locals[parserResult.itemName] = selected || modelCtrl.$viewValue;
2815
- element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
2816
- selected = undefined;
2817
- };
2818
-
2819
- scope.select = function (activeIdx) {
2820
- //called from within the $digest() cycle
2821
- var locals = {};
2822
- var model, item;
2823
- locals[parserResult.itemName] = item = selected = scope.matches[activeIdx].model;
2824
-
2825
- model = parserResult.modelMapper(scope, locals);
2826
- modelCtrl.$setViewValue(model);
2827
- modelCtrl.$render();
2828
- onSelectCallback(scope, {
2829
- $item: item,
2830
- $model: model,
2831
- $label: parserResult.viewMapper(scope, locals)
2832
- });
2833
-
2834
- element[0].focus();
2835
- };
2836
-
2837
- //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
2838
- element.bind('keydown', function (evt) {
2839
-
2840
- //typeahead is open and an "interesting" key was pressed
2841
- if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
2842
- return;
2843
- }
2844
-
2845
- evt.preventDefault();
2846
-
2847
- if (evt.which === 40) {
2848
- scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
2849
- scope.$digest();
2850
-
2851
- } else if (evt.which === 38) {
2852
- scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
2853
- scope.$digest();
2854
-
2855
- } else if (evt.which === 13 || evt.which === 9) {
2856
- scope.$apply(function () {
2857
- scope.select(scope.activeIdx);
2858
- });
2859
-
2860
- } else if (evt.which === 27) {
2861
- evt.stopPropagation();
2862
-
2863
- resetMatches();
2864
- scope.$digest();
2865
- }
2866
- });
2867
-
2868
- $document.bind('click', function(){
2869
- resetMatches();
2870
- scope.$digest();
2871
- });
2872
-
2873
- element.after($compile(popUpEl)(scope));
2874
- }
2875
- };
2876
-
2877
- }])
2878
-
2879
- .directive('typeaheadPopup', function () {
2880
- return {
2881
- restrict:'E',
2882
- scope:{
2883
- matches:'=',
2884
- query:'=',
2885
- active:'=',
2886
- position:'=',
2887
- select:'&'
2888
- },
2889
- replace:true,
2890
- templateUrl:'template/typeahead/typeahead.html',
2891
- link:function (scope, element, attrs) {
2892
-
2893
- scope.isOpen = function () {
2894
- return scope.matches.length > 0;
2895
- };
2896
-
2897
- scope.isActive = function (matchIdx) {
2898
- return scope.active == matchIdx;
2899
- };
2900
-
2901
- scope.selectActive = function (matchIdx) {
2902
- scope.active = matchIdx;
2903
- };
2904
-
2905
- scope.selectMatch = function (activeIdx) {
2906
- scope.select({activeIdx:activeIdx});
2907
- };
2908
- }
2909
- };
2910
- })
2911
-
2912
- .filter('typeaheadHighlight', function() {
2913
-
2914
- function escapeRegexp(queryToEscape) {
2915
- return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
2916
- }
2917
-
2918
- return function(matchItem, query) {
2919
- return query ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : query;
2920
- };
2921
- });angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.position","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
1
+ angular.module("ui.bootstrap", ["ui.bootstrap.transition","ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.position","ui.bootstrap.datepicker","ui.bootstrap.dialog","ui.bootstrap.dropdownToggle","ui.bootstrap.modal","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
2922
2
  angular.module('ui.bootstrap.transition', [])
2923
3
 
2924
4
  /**
@@ -3028,7 +108,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
3028
108
  var initialAnimSkip = true;
3029
109
  scope.$watch(function (){ return element[0].scrollHeight; }, function (value) {
3030
110
  //The listener is called when scollHeight changes
3031
- //It actually does on 2 scenarios:
111
+ //It actually does on 2 scenarios:
3032
112
  // 1. Parent is set to display none
3033
113
  // 2. angular bindings inside are resolved
3034
114
  //When we have a change of scrollHeight we are setting again the correct height if the group is opened
@@ -3042,7 +122,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
3042
122
  }
3043
123
  }
3044
124
  });
3045
-
125
+
3046
126
  scope.$watch(attrs.collapse, function(value) {
3047
127
  if (value) {
3048
128
  collapse();
@@ -3050,7 +130,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
3050
130
  expand();
3051
131
  }
3052
132
  });
3053
-
133
+
3054
134
 
3055
135
  var currentTransition;
3056
136
  var doTransition = function(change) {
@@ -3083,7 +163,7 @@ angular.module('ui.bootstrap.collapse',['ui.bootstrap.transition'])
3083
163
  }
3084
164
  isCollapsed = false;
3085
165
  };
3086
-
166
+
3087
167
  var collapse = function() {
3088
168
  isCollapsed = true;
3089
169
  if (initialAnimSkip) {
@@ -3105,7 +185,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
3105
185
  })
3106
186
 
3107
187
  .controller('AccordionController', ['$scope', '$attrs', 'accordionConfig', function ($scope, $attrs, accordionConfig) {
3108
-
188
+
3109
189
  // This array keeps track of the accordion groups
3110
190
  this.groups = [];
3111
191
 
@@ -3120,7 +200,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
3120
200
  });
3121
201
  }
3122
202
  };
3123
-
203
+
3124
204
  // This is called from the accordion-group directive to add itself to the accordion
3125
205
  this.addGroup = function(groupScope) {
3126
206
  var that = this;
@@ -3173,7 +253,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
3173
253
  accordionCtrl.addGroup(scope);
3174
254
 
3175
255
  scope.isOpen = false;
3176
-
256
+
3177
257
  if ( attrs.isOpen ) {
3178
258
  getIsOpen = $parse(attrs.isOpen);
3179
259
  setIsOpen = getIsOpen.assign;
@@ -3182,7 +262,7 @@ angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
3182
262
  function watchIsOpen() { return getIsOpen(scope.$parent); },
3183
263
  function updateOpen(value) { scope.isOpen = value; }
3184
264
  );
3185
-
265
+
3186
266
  scope.isOpen = getIsOpen ? getIsOpen(scope.$parent) : false;
3187
267
  }
3188
268
 
@@ -3299,21 +379,25 @@ angular.module('ui.bootstrap.buttons', [])
3299
379
  require:'ngModel',
3300
380
  link:function (scope, element, attrs, ngModelCtrl) {
3301
381
 
3302
- var trueValue = scope.$eval(attrs.btnCheckboxTrue);
3303
- var falseValue = scope.$eval(attrs.btnCheckboxFalse);
382
+ function getTrueValue() {
383
+ var trueValue = scope.$eval(attrs.btnCheckboxTrue);
384
+ return angular.isDefined(trueValue) ? trueValue : true;
385
+ }
3304
386
 
3305
- trueValue = angular.isDefined(trueValue) ? trueValue : true;
3306
- falseValue = angular.isDefined(falseValue) ? falseValue : false;
387
+ function getFalseValue() {
388
+ var falseValue = scope.$eval(attrs.btnCheckboxFalse);
389
+ return angular.isDefined(falseValue) ? falseValue : false;
390
+ }
3307
391
 
3308
392
  //model -> UI
3309
393
  ngModelCtrl.$render = function () {
3310
- element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, trueValue));
394
+ element.toggleClass(activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
3311
395
  };
3312
396
 
3313
397
  //ui->model
3314
398
  element.bind(toggleEvent, function () {
3315
399
  scope.$apply(function () {
3316
- ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? falseValue : trueValue);
400
+ ngModelCtrl.$setViewValue(element.hasClass(activeClass) ? getFalseValue() : getTrueValue());
3317
401
  ngModelCtrl.$render();
3318
402
  });
3319
403
  });
@@ -3323,7 +407,7 @@ angular.module('ui.bootstrap.buttons', [])
3323
407
  /**
3324
408
  * @ngdoc overview
3325
409
  * @name ui.bootstrap.carousel
3326
- *
410
+ *
3327
411
  * @description
3328
412
  * AngularJS version of an image carousel.
3329
413
  *
@@ -3354,7 +438,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
3354
438
  }
3355
439
  function goNext() {
3356
440
  //If we have a slide to transition from and we have a transition type and we're allowed, go
3357
- if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
441
+ if (self.currentSlide && angular.isString(direction) && !$scope.noTransition && nextSlide.$element) {
3358
442
  //We shouldn't do class manip in here, but it's the same weird thing bootstrap does. need to fix sometime
3359
443
  nextSlide.$element.addClass(direction);
3360
444
  nextSlide.$element[0].offsetWidth = nextSlide.$element[0].offsetWidth; //force reflow
@@ -3396,7 +480,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
3396
480
 
3397
481
  $scope.next = function() {
3398
482
  var newIndex = (currentIndex + 1) % slides.length;
3399
-
483
+
3400
484
  //Prevent this user-triggered transition from occurring if there is already one in progress
3401
485
  if (!$scope.$currentTransition) {
3402
486
  return self.select(slides[newIndex], 'next');
@@ -3405,7 +489,7 @@ angular.module('ui.bootstrap.carousel', ['ui.bootstrap.transition'])
3405
489
 
3406
490
  $scope.prev = function() {
3407
491
  var newIndex = currentIndex - 1 < 0 ? slides.length - 1 : currentIndex - 1;
3408
-
492
+
3409
493
  //Prevent this user-triggered transition from occurring if there is already one in progress
3410
494
  if (!$scope.$currentTransition) {
3411
495
  return self.select(slides[newIndex], 'prev');
@@ -3622,7 +706,7 @@ function CarouselDemoCtrl($scope) {
3622
706
  var lastValue = scope.active = getActive(scope.$parent);
3623
707
  scope.$watch(function parentActiveWatch() {
3624
708
  var parentActive = getActive(scope.$parent);
3625
-
709
+
3626
710
  if (parentActive !== scope.active) {
3627
711
  // we are out of sync and need to copy
3628
712
  if (parentActive !== lastValue) {
@@ -3652,7 +736,101 @@ function CarouselDemoCtrl($scope) {
3652
736
  };
3653
737
  }]);
3654
738
 
3655
- angular.module('ui.bootstrap.datepicker', [])
739
+ angular.module('ui.bootstrap.position', [])
740
+
741
+ /**
742
+ * A set of utility methods that can be use to retrieve position of DOM elements.
743
+ * It is meant to be used where we need to absolute-position DOM elements in
744
+ * relation to other, existing elements (this is the case for tooltips, popovers,
745
+ * typeahead suggestions etc.).
746
+ */
747
+ .factory('$position', ['$document', '$window', function ($document, $window) {
748
+
749
+ var mouseX, mouseY;
750
+
751
+ $document.bind('mousemove', function mouseMoved(event) {
752
+ mouseX = event.pageX;
753
+ mouseY = event.pageY;
754
+ });
755
+
756
+ function getStyle(el, cssprop) {
757
+ if (el.currentStyle) { //IE
758
+ return el.currentStyle[cssprop];
759
+ } else if ($window.getComputedStyle) {
760
+ return $window.getComputedStyle(el)[cssprop];
761
+ }
762
+ // finally try and get inline style
763
+ return el.style[cssprop];
764
+ }
765
+
766
+ /**
767
+ * Checks if a given element is statically positioned
768
+ * @param element - raw DOM element
769
+ */
770
+ function isStaticPositioned(element) {
771
+ return (getStyle(element, "position") || 'static' ) === 'static';
772
+ }
773
+
774
+ /**
775
+ * returns the closest, non-statically positioned parentOffset of a given element
776
+ * @param element
777
+ */
778
+ var parentOffsetEl = function (element) {
779
+ var docDomEl = $document[0];
780
+ var offsetParent = element.offsetParent || docDomEl;
781
+ while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
782
+ offsetParent = offsetParent.offsetParent;
783
+ }
784
+ return offsetParent || docDomEl;
785
+ };
786
+
787
+ return {
788
+ /**
789
+ * Provides read-only equivalent of jQuery's position function:
790
+ * http://api.jquery.com/position/
791
+ */
792
+ position: function (element) {
793
+ var elBCR = this.offset(element);
794
+ var offsetParentBCR = { top: 0, left: 0 };
795
+ var offsetParentEl = parentOffsetEl(element[0]);
796
+ if (offsetParentEl != $document[0]) {
797
+ offsetParentBCR = this.offset(angular.element(offsetParentEl));
798
+ offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
799
+ offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
800
+ }
801
+
802
+ return {
803
+ width: element.prop('offsetWidth'),
804
+ height: element.prop('offsetHeight'),
805
+ top: elBCR.top - offsetParentBCR.top,
806
+ left: elBCR.left - offsetParentBCR.left
807
+ };
808
+ },
809
+
810
+ /**
811
+ * Provides read-only equivalent of jQuery's offset function:
812
+ * http://api.jquery.com/offset/
813
+ */
814
+ offset: function (element) {
815
+ var boundingClientRect = element[0].getBoundingClientRect();
816
+ return {
817
+ width: element.prop('offsetWidth'),
818
+ height: element.prop('offsetHeight'),
819
+ top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
820
+ left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
821
+ };
822
+ },
823
+
824
+ /**
825
+ * Provides the coordinates of the mouse
826
+ */
827
+ mouse: function () {
828
+ return {x: mouseX, y: mouseY};
829
+ }
830
+ };
831
+ }]);
832
+
833
+ angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.position'])
3656
834
 
3657
835
  .constant('datepickerConfig', {
3658
836
  dayFormat: 'dd',
@@ -3663,31 +841,139 @@ angular.module('ui.bootstrap.datepicker', [])
3663
841
  monthTitleFormat: 'yyyy',
3664
842
  showWeeks: true,
3665
843
  startingDay: 0,
3666
- yearRange: 20
844
+ yearRange: 20,
845
+ minDate: null,
846
+ maxDate: null
3667
847
  })
3668
848
 
3669
- .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', function (dateFilter, $parse, datepickerConfig) {
849
+ .controller('DatepickerController', ['$scope', '$attrs', 'dateFilter', 'datepickerConfig', function($scope, $attrs, dateFilter, dtConfig) {
850
+ var format = {
851
+ day: getValue($attrs.dayFormat, dtConfig.dayFormat),
852
+ month: getValue($attrs.monthFormat, dtConfig.monthFormat),
853
+ year: getValue($attrs.yearFormat, dtConfig.yearFormat),
854
+ dayHeader: getValue($attrs.dayHeaderFormat, dtConfig.dayHeaderFormat),
855
+ dayTitle: getValue($attrs.dayTitleFormat, dtConfig.dayTitleFormat),
856
+ monthTitle: getValue($attrs.monthTitleFormat, dtConfig.monthTitleFormat)
857
+ },
858
+ startingDay = getValue($attrs.startingDay, dtConfig.startingDay),
859
+ yearRange = getValue($attrs.yearRange, dtConfig.yearRange);
860
+
861
+ this.minDate = dtConfig.minDate ? new Date(dtConfig.minDate) : null;
862
+ this.maxDate = dtConfig.maxDate ? new Date(dtConfig.maxDate) : null;
863
+
864
+ function getValue(value, defaultValue) {
865
+ return angular.isDefined(value) ? $scope.$parent.$eval(value) : defaultValue;
866
+ }
867
+
868
+ function getDaysInMonth( year, month ) {
869
+ return new Date(year, month, 0).getDate();
870
+ }
871
+
872
+ function getDates(startDate, n) {
873
+ var dates = new Array(n);
874
+ var current = startDate, i = 0;
875
+ while (i < n) {
876
+ dates[i++] = new Date(current);
877
+ current.setDate( current.getDate() + 1 );
878
+ }
879
+ return dates;
880
+ }
881
+
882
+ function makeDate(date, format, isSelected, isSecondary) {
883
+ return { date: date, label: dateFilter(date, format), selected: !!isSelected, secondary: !!isSecondary };
884
+ }
885
+
886
+ this.modes = [
887
+ {
888
+ name: 'day',
889
+ getVisibleDates: function(date, selected) {
890
+ var year = date.getFullYear(), month = date.getMonth(), firstDayOfMonth = new Date(year, month, 1);
891
+ var difference = startingDay - firstDayOfMonth.getDay(),
892
+ numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference,
893
+ firstDate = new Date(firstDayOfMonth), numDates = 0;
894
+
895
+ if ( numDisplayedFromPreviousMonth > 0 ) {
896
+ firstDate.setDate( - numDisplayedFromPreviousMonth + 1 );
897
+ numDates += numDisplayedFromPreviousMonth; // Previous
898
+ }
899
+ numDates += getDaysInMonth(year, month + 1); // Current
900
+ numDates += (7 - numDates % 7) % 7; // Next
901
+
902
+ var days = getDates(firstDate, numDates), labels = new Array(7);
903
+ for (var i = 0; i < numDates; i ++) {
904
+ var dt = new Date(days[i]);
905
+ days[i] = makeDate(dt, format.day, (selected && selected.getDate() === dt.getDate() && selected.getMonth() === dt.getMonth() && selected.getFullYear() === dt.getFullYear()), dt.getMonth() !== month);
906
+ }
907
+ for (var j = 0; j < 7; j++) {
908
+ labels[j] = dateFilter(days[j].date, format.dayHeader);
909
+ }
910
+ return { objects: days, title: dateFilter(date, format.dayTitle), labels: labels };
911
+ },
912
+ compare: function(date1, date2) {
913
+ return (new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) - new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) );
914
+ },
915
+ split: 7,
916
+ step: { months: 1 }
917
+ },
918
+ {
919
+ name: 'month',
920
+ getVisibleDates: function(date, selected) {
921
+ var months = new Array(12), year = date.getFullYear();
922
+ for ( var i = 0; i < 12; i++ ) {
923
+ var dt = new Date(year, i, 1);
924
+ months[i] = makeDate(dt, format.month, (selected && selected.getMonth() === i && selected.getFullYear() === year));
925
+ }
926
+ return { objects: months, title: dateFilter(date, format.monthTitle) };
927
+ },
928
+ compare: function(date1, date2) {
929
+ return new Date( date1.getFullYear(), date1.getMonth() ) - new Date( date2.getFullYear(), date2.getMonth() );
930
+ },
931
+ split: 3,
932
+ step: { years: 1 }
933
+ },
934
+ {
935
+ name: 'year',
936
+ getVisibleDates: function(date, selected) {
937
+ var years = new Array(yearRange), year = date.getFullYear(), startYear = parseInt((year - 1) / yearRange, 10) * yearRange + 1;
938
+ for ( var i = 0; i < yearRange; i++ ) {
939
+ var dt = new Date(startYear + i, 0, 1);
940
+ years[i] = makeDate(dt, format.year, (selected && selected.getFullYear() === dt.getFullYear()));
941
+ }
942
+ return { objects: years, title: [years[0].label, years[yearRange - 1].label].join(' - ') };
943
+ },
944
+ compare: function(date1, date2) {
945
+ return date1.getFullYear() - date2.getFullYear();
946
+ },
947
+ split: 5,
948
+ step: { years: yearRange }
949
+ }
950
+ ];
951
+
952
+ this.isDisabled = function(date, mode) {
953
+ var currentMode = this.modes[mode || 0];
954
+ return ((this.minDate && currentMode.compare(date, this.minDate) < 0) || (this.maxDate && currentMode.compare(date, this.maxDate) > 0) || ($scope.dateDisabled && $scope.dateDisabled({date: date, mode: currentMode.name})));
955
+ };
956
+ }])
957
+
958
+ .directive( 'datepicker', ['dateFilter', '$parse', 'datepickerConfig', '$log', function (dateFilter, $parse, datepickerConfig, $log) {
3670
959
  return {
3671
960
  restrict: 'EA',
3672
961
  replace: true,
962
+ templateUrl: 'template/datepicker/datepicker.html',
3673
963
  scope: {
3674
- model: '=ngModel',
3675
964
  dateDisabled: '&'
3676
965
  },
3677
- templateUrl: 'template/datepicker/datepicker.html',
3678
- link: function(scope, element, attrs) {
3679
- scope.mode = 'day'; // Initial mode
966
+ require: ['datepicker', '?^ngModel'],
967
+ controller: 'DatepickerController',
968
+ link: function(scope, element, attrs, ctrls) {
969
+ var datepickerCtrl = ctrls[0], ngModel = ctrls[1];
970
+
971
+ if (!ngModel) {
972
+ return; // do nothing if no ng-model
973
+ }
3680
974
 
3681
975
  // Configuration parameters
3682
- var selected = new Date(), showWeeks, minDate, maxDate, format = {};
3683
- format.day = angular.isDefined(attrs.dayFormat) ? scope.$eval(attrs.dayFormat) : datepickerConfig.dayFormat;
3684
- format.month = angular.isDefined(attrs.monthFormat) ? scope.$eval(attrs.monthFormat) : datepickerConfig.monthFormat;
3685
- format.year = angular.isDefined(attrs.yearFormat) ? scope.$eval(attrs.yearFormat) : datepickerConfig.yearFormat;
3686
- format.dayHeader = angular.isDefined(attrs.dayHeaderFormat) ? scope.$eval(attrs.dayHeaderFormat) : datepickerConfig.dayHeaderFormat;
3687
- format.dayTitle = angular.isDefined(attrs.dayTitleFormat) ? scope.$eval(attrs.dayTitleFormat) : datepickerConfig.dayTitleFormat;
3688
- format.monthTitle = angular.isDefined(attrs.monthTitleFormat) ? scope.$eval(attrs.monthTitleFormat) : datepickerConfig.monthTitleFormat;
3689
- var startingDay = angular.isDefined(attrs.startingDay) ? scope.$eval(attrs.startingDay) : datepickerConfig.startingDay;
3690
- var yearRange = angular.isDefined(attrs.yearRange) ? scope.$eval(attrs.yearRange) : datepickerConfig.yearRange;
976
+ var mode = 0, selected = new Date(), showWeeks = datepickerConfig.showWeeks;
3691
977
 
3692
978
  if (attrs.showWeeks) {
3693
979
  scope.$parent.$watch($parse(attrs.showWeeks), function(value) {
@@ -3695,174 +981,282 @@ angular.module('ui.bootstrap.datepicker', [])
3695
981
  updateShowWeekNumbers();
3696
982
  });
3697
983
  } else {
3698
- showWeeks = datepickerConfig.showWeeks;
3699
984
  updateShowWeekNumbers();
3700
985
  }
3701
986
 
3702
987
  if (attrs.min) {
3703
988
  scope.$parent.$watch($parse(attrs.min), function(value) {
3704
- minDate = new Date(value);
989
+ datepickerCtrl.minDate = value ? new Date(value) : null;
3705
990
  refill();
3706
991
  });
3707
992
  }
3708
993
  if (attrs.max) {
3709
994
  scope.$parent.$watch($parse(attrs.max), function(value) {
3710
- maxDate = new Date(value);
995
+ datepickerCtrl.maxDate = value ? new Date(value) : null;
3711
996
  refill();
3712
997
  });
3713
998
  }
3714
999
 
3715
- function updateCalendar (rows, labels, title) {
3716
- scope.rows = rows;
3717
- scope.labels = labels;
3718
- scope.title = title;
1000
+ function updateShowWeekNumbers() {
1001
+ scope.showWeekNumbers = mode === 0 && showWeeks;
3719
1002
  }
3720
1003
 
3721
- // Define whether the week number are visible
3722
- function updateShowWeekNumbers() {
3723
- scope.showWeekNumbers = ( scope.mode === 'day' && showWeeks );
1004
+ // Split array into smaller arrays
1005
+ function split(arr, size) {
1006
+ var arrays = [];
1007
+ while (arr.length > 0) {
1008
+ arrays.push(arr.splice(0, size));
1009
+ }
1010
+ return arrays;
3724
1011
  }
3725
1012
 
3726
- function compare( date1, date2 ) {
3727
- if ( scope.mode === 'year') {
3728
- return date2.getFullYear() - date1.getFullYear();
3729
- } else if ( scope.mode === 'month' ) {
3730
- return new Date( date2.getFullYear(), date2.getMonth() ) - new Date( date1.getFullYear(), date1.getMonth() );
3731
- } else if ( scope.mode === 'day' ) {
3732
- return (new Date( date2.getFullYear(), date2.getMonth(), date2.getDate() ) - new Date( date1.getFullYear(), date1.getMonth(), date1.getDate() ) );
1013
+ function refill( updateSelected ) {
1014
+ var date = null, valid = true;
1015
+
1016
+ if ( ngModel.$modelValue ) {
1017
+ date = new Date( ngModel.$modelValue );
1018
+
1019
+ if ( isNaN(date) ) {
1020
+ valid = false;
1021
+ $log.error('Datepicker directive: "ng-model" value must be a Date object, a number of milliseconds since 01.01.1970 or a string representing an RFC2822 or ISO 8601 date.');
1022
+ } else if ( updateSelected ) {
1023
+ selected = date;
1024
+ }
3733
1025
  }
1026
+ ngModel.$setValidity('date', valid);
1027
+
1028
+ var currentMode = datepickerCtrl.modes[mode], data = currentMode.getVisibleDates(selected, date);
1029
+ angular.forEach(data.objects, function(obj) {
1030
+ obj.disabled = datepickerCtrl.isDisabled(obj.date, mode);
1031
+ });
1032
+
1033
+ ngModel.$setValidity('date-disabled', (!date || !datepickerCtrl.isDisabled(date)));
1034
+
1035
+ scope.rows = split(data.objects, currentMode.split);
1036
+ scope.labels = data.labels || [];
1037
+ scope.title = data.title;
3734
1038
  }
3735
1039
 
3736
- function isDisabled(date) {
3737
- return ((minDate && compare(date, minDate) > 0) || (maxDate && compare(date, maxDate) < 0) || (scope.dateDisabled && scope.dateDisabled({ date: date, mode: scope.mode })));
1040
+ function setMode(value) {
1041
+ mode = value;
1042
+ updateShowWeekNumbers();
1043
+ refill();
3738
1044
  }
3739
1045
 
3740
- // Split array into smaller arrays
3741
- var split = function(a, size) {
3742
- var arrays = [];
3743
- while (a.length > 0) {
3744
- arrays.push(a.splice(0, size));
1046
+ ngModel.$render = function() {
1047
+ refill( true );
1048
+ };
1049
+
1050
+ scope.select = function( date ) {
1051
+ if ( mode === 0 ) {
1052
+ var dt = new Date( ngModel.$modelValue );
1053
+ dt.setFullYear( date.getFullYear(), date.getMonth(), date.getDate() );
1054
+ ngModel.$setViewValue( dt );
1055
+ refill( true );
1056
+ } else {
1057
+ selected = date;
1058
+ setMode( mode - 1 );
3745
1059
  }
3746
- return arrays;
3747
1060
  };
3748
- var getDaysInMonth = function( year, month ) {
3749
- return new Date(year, month + 1, 0).getDate();
1061
+ scope.move = function(direction) {
1062
+ var step = datepickerCtrl.modes[mode].step;
1063
+ selected.setMonth( selected.getMonth() + direction * (step.months || 0) );
1064
+ selected.setFullYear( selected.getFullYear() + direction * (step.years || 0) );
1065
+ refill();
1066
+ };
1067
+ scope.toggleMode = function() {
1068
+ setMode( (mode + 1) % datepickerCtrl.modes.length );
1069
+ };
1070
+ scope.getWeekNumber = function(row) {
1071
+ return ( mode === 0 && scope.showWeekNumbers && row.length === 7 ) ? getISO8601WeekNumber(row[0].date) : null;
3750
1072
  };
3751
1073
 
3752
- var fill = {
3753
- day: function() {
3754
- var days = [], labels = [], lastDate = null;
1074
+ function getISO8601WeekNumber(date) {
1075
+ var checkDate = new Date(date);
1076
+ checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1077
+ var time = checkDate.getTime();
1078
+ checkDate.setMonth(0); // Compare with Jan 1
1079
+ checkDate.setDate(1);
1080
+ return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1081
+ }
1082
+ }
1083
+ };
1084
+ }])
3755
1085
 
3756
- function addDays( dt, n, isCurrentMonth ) {
3757
- for (var i =0; i < n; i ++) {
3758
- days.push( {date: new Date(dt), isCurrent: isCurrentMonth, isSelected: isSelected(dt), label: dateFilter(dt, format.day), disabled: isDisabled(dt) } );
3759
- dt.setDate( dt.getDate() + 1 );
3760
- }
3761
- lastDate = dt;
3762
- }
1086
+ .constant('datepickerPopupConfig', {
1087
+ dateFormat: 'yyyy-MM-dd',
1088
+ closeOnDateSelection: true
1089
+ })
3763
1090
 
3764
- var d = new Date(selected);
3765
- d.setDate(1);
1091
+ .directive('datepickerPopup', ['$compile', '$parse', '$document', '$position', 'dateFilter', 'datepickerPopupConfig',
1092
+ function ($compile, $parse, $document, $position, dateFilter, datepickerPopupConfig) {
1093
+ return {
1094
+ restrict: 'EA',
1095
+ require: 'ngModel',
1096
+ link: function(originalScope, element, attrs, ngModel) {
3766
1097
 
3767
- var difference = startingDay - d.getDay();
3768
- var numDisplayedFromPreviousMonth = (difference > 0) ? 7 - difference : - difference;
1098
+ var closeOnDateSelection = angular.isDefined(attrs.closeOnDateSelection) ? scope.$eval(attrs.closeOnDateSelection) : datepickerPopupConfig.closeOnDateSelection;
1099
+ var dateFormat = attrs.datepickerPopup || datepickerPopupConfig.dateFormat;
3769
1100
 
3770
- if ( numDisplayedFromPreviousMonth > 0 ) {
3771
- d.setDate( - numDisplayedFromPreviousMonth + 1 );
3772
- addDays(d, numDisplayedFromPreviousMonth, false);
3773
- }
3774
- addDays(lastDate || d, getDaysInMonth(selected.getFullYear(), selected.getMonth()), true);
3775
- addDays(lastDate, (7 - days.length % 7) % 7, false);
1101
+ // create a child scope for the datepicker directive so we are not polluting original scope
1102
+ var scope = originalScope.$new();
1103
+ originalScope.$on('$destroy', function() {
1104
+ scope.$destroy();
1105
+ });
3776
1106
 
3777
- // Day labels
3778
- for (i = 0; i < 7; i++) {
3779
- labels.push( dateFilter(days[i].date, format.dayHeader) );
3780
- }
3781
- updateCalendar( split( days, 7 ), labels, dateFilter(selected, format.dayTitle) );
3782
- },
3783
- month: function() {
3784
- var months = [], i = 0, year = selected.getFullYear();
3785
- while ( i < 12 ) {
3786
- var dt = new Date(year, i++, 1);
3787
- months.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.month), disabled: isDisabled(dt)} );
3788
- }
3789
- updateCalendar( split( months, 3 ), [], dateFilter(selected, format.monthTitle) );
3790
- },
3791
- year: function() {
3792
- var years = [], year = parseInt((selected.getFullYear() - 1) / yearRange, 10) * yearRange + 1;
3793
- for ( var i = 0; i < yearRange; i++ ) {
3794
- var dt = new Date(year + i, 0, 1);
3795
- years.push( {date: dt, isCurrent: true, isSelected: isSelected(dt), label: dateFilter(dt, format.year), disabled: isDisabled(dt)} );
1107
+ function formatDate(value) {
1108
+ return (value) ? dateFilter(value, dateFormat) : null;
1109
+ }
1110
+ ngModel.$formatters.push(formatDate);
1111
+
1112
+ // TODO: reverse from dateFilter string to Date object
1113
+ function parseDate(value) {
1114
+ if ( value ) {
1115
+ var date = new Date(value);
1116
+ if (!isNaN(date)) {
1117
+ return date;
3796
1118
  }
3797
- var title = years[0].label + ' - ' + years[years.length - 1].label;
3798
- updateCalendar( split( years, 5 ), [], title );
1119
+ }
1120
+ return value;
1121
+ }
1122
+ ngModel.$parsers.push(parseDate);
1123
+
1124
+ var getIsOpen, setIsOpen;
1125
+ if ( attrs.open ) {
1126
+ getIsOpen = $parse(attrs.open);
1127
+ setIsOpen = getIsOpen.assign;
1128
+
1129
+ originalScope.$watch(getIsOpen, function updateOpen(value) {
1130
+ scope.isOpen = !! value;
1131
+ });
1132
+ }
1133
+ scope.isOpen = getIsOpen ? getIsOpen(originalScope) : false; // Initial state
1134
+
1135
+ function setOpen( value ) {
1136
+ if (setIsOpen) {
1137
+ setIsOpen(originalScope, !!value);
1138
+ } else {
1139
+ scope.isOpen = !!value;
1140
+ }
1141
+ }
1142
+
1143
+ var documentClickBind = function(event) {
1144
+ if (scope.isOpen && event.target !== element[0]) {
1145
+ scope.$apply(function() {
1146
+ setOpen(false);
1147
+ });
1148
+ }
1149
+ };
1150
+
1151
+ var elementFocusBind = function() {
1152
+ scope.$apply(function() {
1153
+ setOpen( true );
1154
+ });
1155
+ };
1156
+
1157
+ // popup element used to display calendar
1158
+ var popupEl = angular.element('<datepicker-popup-wrap><datepicker></datepicker></datepicker-popup-wrap>');
1159
+ popupEl.attr({
1160
+ 'ng-model': 'date',
1161
+ 'ng-change': 'dateSelection()'
1162
+ });
1163
+ var datepickerEl = popupEl.find('datepicker');
1164
+ if (attrs.datepickerOptions) {
1165
+ datepickerEl.attr(angular.extend({}, originalScope.$eval(attrs.datepickerOptions)));
1166
+ }
1167
+
1168
+ var $setModelValue = $parse(attrs.ngModel).assign;
1169
+
1170
+ // Inner change
1171
+ scope.dateSelection = function() {
1172
+ $setModelValue(originalScope, scope.date);
1173
+ if (closeOnDateSelection) {
1174
+ setOpen( false );
3799
1175
  }
3800
1176
  };
3801
- var refill = function() {
3802
- fill[scope.mode]();
3803
- };
3804
- var isSelected = function( dt ) {
3805
- if ( scope.model && scope.model.getFullYear() === dt.getFullYear() ) {
3806
- if ( scope.mode === 'year' ) {
3807
- return true;
3808
- }
3809
- if ( scope.model.getMonth() === dt.getMonth() ) {
3810
- return ( scope.mode === 'month' || (scope.mode === 'day' && scope.model.getDate() === dt.getDate()) );
1177
+
1178
+ // Outter change
1179
+ scope.$watch(function() {
1180
+ return ngModel.$modelValue;
1181
+ }, function(value) {
1182
+ if (angular.isString(value)) {
1183
+ var date = parseDate(value);
1184
+
1185
+ if (value && !date) {
1186
+ $setModelValue(originalScope, null);
1187
+ throw new Error(value + ' cannot be parsed to a date object.');
1188
+ } else {
1189
+ value = date;
3811
1190
  }
3812
1191
  }
3813
- return false;
3814
- };
1192
+ scope.date = value;
1193
+ updatePosition();
1194
+ });
3815
1195
 
3816
- scope.$watch('model', function ( dt, olddt ) {
3817
- if ( angular.isDate(dt) ) {
3818
- selected = angular.copy(dt);
1196
+ function addWatchableAttribute(attribute, scopeProperty, datepickerAttribute) {
1197
+ if (attribute) {
1198
+ originalScope.$watch($parse(attribute), function(value){
1199
+ scope[scopeProperty] = value;
1200
+ });
1201
+ datepickerEl.attr(datepickerAttribute || scopeProperty, scopeProperty);
3819
1202
  }
1203
+ }
1204
+ addWatchableAttribute(attrs.min, 'min');
1205
+ addWatchableAttribute(attrs.max, 'max');
1206
+ if (attrs.showWeeks) {
1207
+ addWatchableAttribute(attrs.showWeeks, 'showWeeks', 'show-weeks');
1208
+ } else {
1209
+ scope.showWeeks = true;
1210
+ datepickerEl.attr('show-weeks', 'showWeeks');
1211
+ }
1212
+ if (attrs.dateDisabled) {
1213
+ datepickerEl.attr('date-disabled', attrs.dateDisabled);
1214
+ }
3820
1215
 
3821
- if ( ! angular.equals(dt, olddt) ) {
3822
- refill();
3823
- }
3824
- });
3825
- scope.$watch('mode', function() {
3826
- updateShowWeekNumbers();
3827
- refill();
3828
- });
1216
+ function updatePosition() {
1217
+ scope.position = $position.position(element);
1218
+ scope.position.top = scope.position.top + element.prop('offsetHeight');
1219
+ }
3829
1220
 
3830
- scope.select = function( dt ) {
3831
- selected = new Date(dt);
3832
-
3833
- if ( scope.mode === 'year' ) {
3834
- scope.mode = 'month';
3835
- selected.setFullYear( dt.getFullYear() );
3836
- } else if ( scope.mode === 'month' ) {
3837
- scope.mode = 'day';
3838
- selected.setMonth( dt.getMonth() );
3839
- } else if ( scope.mode === 'day' ) {
3840
- scope.model = new Date(selected);
1221
+ scope.$watch('isOpen', function(value) {
1222
+ if (value) {
1223
+ updatePosition();
1224
+ $document.bind('click', documentClickBind);
1225
+ element.unbind('focus', elementFocusBind);
1226
+ element.focus();
1227
+ } else {
1228
+ $document.unbind('click', documentClickBind);
1229
+ element.bind('focus', elementFocusBind);
3841
1230
  }
3842
- };
3843
- scope.move = function(step) {
3844
- if (scope.mode === 'day') {
3845
- selected.setMonth( selected.getMonth() + step );
3846
- } else if (scope.mode === 'month') {
3847
- selected.setFullYear( selected.getFullYear() + step );
3848
- } else if (scope.mode === 'year') {
3849
- selected.setFullYear( selected.getFullYear() + step * yearRange );
1231
+
1232
+ if ( setIsOpen ) {
1233
+ setIsOpen(originalScope, value);
3850
1234
  }
3851
- refill();
1235
+ });
1236
+
1237
+ scope.today = function() {
1238
+ $setModelValue(originalScope, new Date());
3852
1239
  };
3853
- scope.toggleMode = function() {
3854
- scope.mode = ( scope.mode === 'day' ) ? 'month' : ( scope.mode === 'month' ) ? 'year' : 'day';
1240
+ scope.clear = function() {
1241
+ $setModelValue(originalScope, null);
3855
1242
  };
3856
- scope.getWeekNumber = function(row) {
3857
- if ( scope.mode !== 'day' || ! scope.showWeekNumbers || row.length !== 7 ) {
3858
- return;
3859
- }
3860
1243
 
3861
- var index = ( startingDay > 4 ) ? 11 - startingDay : 4 - startingDay; // Thursday
3862
- var d = new Date( row[ index ].date );
3863
- d.setHours(0, 0, 0);
3864
- return Math.ceil((((d - new Date(d.getFullYear(), 0, 1)) / 86400000) + 1) / 7); // 86400000 = 1000*60*60*24;
3865
- };
1244
+ element.after($compile(popupEl)(scope));
1245
+ }
1246
+ };
1247
+ }])
1248
+
1249
+ .directive('datepickerPopupWrap', [function() {
1250
+ return {
1251
+ restrict:'E',
1252
+ replace: true,
1253
+ transclude: true,
1254
+ templateUrl: 'template/datepicker/popup.html',
1255
+ link:function (scope, element, attrs) {
1256
+ element.bind('click', function(event) {
1257
+ event.preventDefault();
1258
+ event.stopPropagation();
1259
+ });
3866
1260
  }
3867
1261
  };
3868
1262
  }]);
@@ -3894,9 +1288,9 @@ dialogModule.provider("$dialog", function(){
3894
1288
  keyboard: true, // close with esc key
3895
1289
  backdropClick: true // only in conjunction with backdrop=true
3896
1290
  /* other options: template, templateUrl, controller */
3897
- };
1291
+ };
3898
1292
 
3899
- var globalOptions = {};
1293
+ var globalOptions = {};
3900
1294
 
3901
1295
  var activeBackdrops = {value : 0};
3902
1296
 
@@ -3906,21 +1300,21 @@ dialogModule.provider("$dialog", function(){
3906
1300
  // // don't close dialog when backdrop is clicked by default
3907
1301
  // $dialogProvider.options({backdropClick: false});
3908
1302
  // });
3909
- this.options = function(value){
3910
- globalOptions = value;
3911
- };
1303
+ this.options = function(value){
1304
+ globalOptions = value;
1305
+ };
3912
1306
 
3913
1307
  // Returns the actual `$dialog` service that is injected in controllers
3914
- this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
1308
+ this.$get = ["$http", "$document", "$compile", "$rootScope", "$controller", "$templateCache", "$q", "$transition", "$injector",
3915
1309
  function ($http, $document, $compile, $rootScope, $controller, $templateCache, $q, $transition, $injector) {
3916
1310
 
3917
- var body = $document.find('body');
1311
+ var body = $document.find('body');
3918
1312
 
3919
- function createElement(clazz) {
3920
- var el = angular.element("<div>");
3921
- el.addClass(clazz);
3922
- return el;
3923
- }
1313
+ function createElement(clazz) {
1314
+ var el = angular.element("<div>");
1315
+ el.addClass(clazz);
1316
+ return el;
1317
+ }
3924
1318
 
3925
1319
  // The `Dialog` class represents a modal dialog. The dialog class can be invoked by providing an options object
3926
1320
  // containing at lest template or templateUrl and controller:
@@ -3930,7 +1324,7 @@ dialogModule.provider("$dialog", function(){
3930
1324
  // Dialogs can also be created using templateUrl and controller as distinct arguments:
3931
1325
  //
3932
1326
  // var d = new Dialog('path/to/dialog.html', MyDialogController);
3933
- function Dialog(opts) {
1327
+ function Dialog(opts) {
3934
1328
 
3935
1329
  var self = this, options = this.options = angular.extend({}, defaults, globalOptions, opts);
3936
1330
  this._open = false;
@@ -3960,10 +1354,6 @@ dialogModule.provider("$dialog", function(){
3960
1354
  e.preventDefault();
3961
1355
  self.$scope.$apply();
3962
1356
  };
3963
-
3964
- this.handleLocationChange = function() {
3965
- self.close();
3966
- };
3967
1357
  }
3968
1358
 
3969
1359
  // The `isOpen()` method returns wether the dialog is currently visible.
@@ -4070,9 +1460,9 @@ dialogModule.provider("$dialog", function(){
4070
1460
  Dialog.prototype._addElementsToDom = function(){
4071
1461
  body.append(this.modalEl);
4072
1462
 
4073
- if(this.options.backdrop) {
1463
+ if(this.options.backdrop) {
4074
1464
  if (activeBackdrops.value === 0) {
4075
- body.append(this.backdropEl);
1465
+ body.append(this.backdropEl);
4076
1466
  }
4077
1467
  activeBackdrops.value++;
4078
1468
  }
@@ -4083,10 +1473,10 @@ dialogModule.provider("$dialog", function(){
4083
1473
  Dialog.prototype._removeElementsFromDom = function(){
4084
1474
  this.modalEl.remove();
4085
1475
 
4086
- if(this.options.backdrop) {
1476
+ if(this.options.backdrop) {
4087
1477
  activeBackdrops.value--;
4088
1478
  if (activeBackdrops.value === 0) {
4089
- this.backdropEl.remove();
1479
+ this.backdropEl.remove();
4090
1480
  }
4091
1481
  }
4092
1482
  this._open = false;
@@ -4216,7 +1606,7 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
4216
1606
  // Create a dialog with the template as the contents of the directive
4217
1607
  // Add the current scope as the resolve in order to make the directive scope as a dialog controller scope
4218
1608
  opts = angular.extend(opts, {
4219
- template: elm.html(),
1609
+ template: elm.html(),
4220
1610
  resolve: { $scope: function() { return scope; } }
4221
1611
  });
4222
1612
  var dialog = $dialog.dialog(opts);
@@ -4228,9 +1618,9 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
4228
1618
  $parse(attrs.close)(scope);
4229
1619
  };
4230
1620
  } else {
4231
- setClosed = function() {
1621
+ setClosed = function() {
4232
1622
  if (angular.isFunction($parse(shownExpr).assign)) {
4233
- $parse(shownExpr).assign(scope, false);
1623
+ $parse(shownExpr).assign(scope, false);
4234
1624
  }
4235
1625
  };
4236
1626
  }
@@ -4252,25 +1642,41 @@ angular.module('ui.bootstrap.modal', ['ui.bootstrap.dialog'])
4252
1642
  }]);
4253
1643
  angular.module('ui.bootstrap.pagination', [])
4254
1644
 
4255
- .controller('PaginationController', ['$scope', function (scope) {
1645
+ .controller('PaginationController', ['$scope', '$interpolate', function ($scope, $interpolate) {
1646
+
1647
+ this.currentPage = 1;
4256
1648
 
4257
- scope.noPrevious = function() {
4258
- return scope.currentPage === 1;
1649
+ this.noPrevious = function() {
1650
+ return this.currentPage === 1;
4259
1651
  };
4260
- scope.noNext = function() {
4261
- return scope.currentPage === scope.numPages;
1652
+ this.noNext = function() {
1653
+ return this.currentPage === $scope.numPages;
4262
1654
  };
4263
1655
 
4264
- scope.isActive = function(page) {
4265
- return scope.currentPage === page;
1656
+ this.isActive = function(page) {
1657
+ return this.currentPage === page;
4266
1658
  };
4267
1659
 
4268
- scope.selectPage = function(page) {
4269
- if ( ! scope.isActive(page) && page > 0 && page <= scope.numPages) {
4270
- scope.currentPage = page;
4271
- scope.onSelectPage({ page: page });
1660
+ this.reset = function() {
1661
+ $scope.pages = [];
1662
+ this.currentPage = parseInt($scope.currentPage, 10);
1663
+
1664
+ if ( this.currentPage > $scope.numPages ) {
1665
+ $scope.selectPage($scope.numPages);
1666
+ }
1667
+ };
1668
+
1669
+ var self = this;
1670
+ $scope.selectPage = function(page) {
1671
+ if ( ! self.isActive(page) && page > 0 && page <= $scope.numPages) {
1672
+ $scope.currentPage = page;
1673
+ $scope.onSelectPage({ page: page });
4272
1674
  }
4273
1675
  };
1676
+
1677
+ this.getAttributeValue = function(attribute, defaultValue, interpolate) {
1678
+ return angular.isDefined(attribute) ? (interpolate ? $interpolate(attribute)($scope.$parent) : $scope.$parent.$eval(attribute)) : defaultValue;
1679
+ };
4274
1680
  }])
4275
1681
 
4276
1682
  .constant('paginationConfig', {
@@ -4283,7 +1689,7 @@ angular.module('ui.bootstrap.pagination', [])
4283
1689
  rotate: true
4284
1690
  })
4285
1691
 
4286
- .directive('pagination', ['paginationConfig', function(paginationConfig) {
1692
+ .directive('pagination', ['paginationConfig', function(config) {
4287
1693
  return {
4288
1694
  restrict: 'EA',
4289
1695
  scope: {
@@ -4295,16 +1701,16 @@ angular.module('ui.bootstrap.pagination', [])
4295
1701
  controller: 'PaginationController',
4296
1702
  templateUrl: 'template/pagination/pagination.html',
4297
1703
  replace: true,
4298
- link: function(scope, element, attrs) {
1704
+ link: function(scope, element, attrs, paginationCtrl) {
4299
1705
 
4300
1706
  // Setup configuration parameters
4301
- var boundaryLinks = angular.isDefined(attrs.boundaryLinks) ? scope.$eval(attrs.boundaryLinks) : paginationConfig.boundaryLinks;
4302
- var directionLinks = angular.isDefined(attrs.directionLinks) ? scope.$eval(attrs.directionLinks) : paginationConfig.directionLinks;
4303
- var firstText = angular.isDefined(attrs.firstText) ? scope.$parent.$eval(attrs.firstText) : paginationConfig.firstText;
4304
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : paginationConfig.previousText;
4305
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : paginationConfig.nextText;
4306
- var lastText = angular.isDefined(attrs.lastText) ? scope.$parent.$eval(attrs.lastText) : paginationConfig.lastText;
4307
- var rotate = angular.isDefined(attrs.rotate) ? scope.$eval(attrs.rotate) : paginationConfig.rotate;
1707
+ var boundaryLinks = paginationCtrl.getAttributeValue(attrs.boundaryLinks, config.boundaryLinks ),
1708
+ directionLinks = paginationCtrl.getAttributeValue(attrs.directionLinks, config.directionLinks ),
1709
+ firstText = paginationCtrl.getAttributeValue(attrs.firstText, config.firstText, true),
1710
+ previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1711
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1712
+ lastText = paginationCtrl.getAttributeValue(attrs.lastText, config.lastText, true),
1713
+ rotate = paginationCtrl.getAttributeValue(attrs.rotate, config.rotate);
4308
1714
 
4309
1715
  // Create page object used in template
4310
1716
  function makePage(number, text, isActive, isDisabled) {
@@ -4317,7 +1723,7 @@ angular.module('ui.bootstrap.pagination', [])
4317
1723
  }
4318
1724
 
4319
1725
  scope.$watch('numPages + currentPage + maxSize', function() {
4320
- scope.pages = [];
1726
+ paginationCtrl.reset();
4321
1727
 
4322
1728
  // Default page limits
4323
1729
  var startPage = 1, endPage = scope.numPages;
@@ -4327,7 +1733,7 @@ angular.module('ui.bootstrap.pagination', [])
4327
1733
  if ( isMaxSized ) {
4328
1734
  if ( rotate ) {
4329
1735
  // Current page is displayed in the middle of the visible ones
4330
- startPage = Math.max(scope.currentPage - Math.floor(scope.maxSize/2), 1);
1736
+ startPage = Math.max(paginationCtrl.currentPage - Math.floor(scope.maxSize/2), 1);
4331
1737
  endPage = startPage + scope.maxSize - 1;
4332
1738
 
4333
1739
  // Adjust if limit is exceeded
@@ -4337,7 +1743,7 @@ angular.module('ui.bootstrap.pagination', [])
4337
1743
  }
4338
1744
  } else {
4339
1745
  // Visible pages are paginated with maxSize
4340
- startPage = ((Math.ceil(scope.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
1746
+ startPage = ((Math.ceil(paginationCtrl.currentPage / scope.maxSize) - 1) * scope.maxSize) + 1;
4341
1747
 
4342
1748
  // Adjust last page if limit is exceeded
4343
1749
  endPage = Math.min(startPage + scope.maxSize - 1, scope.numPages);
@@ -4346,7 +1752,7 @@ angular.module('ui.bootstrap.pagination', [])
4346
1752
 
4347
1753
  // Add page number links
4348
1754
  for (var number = startPage; number <= endPage; number++) {
4349
- var page = makePage(number, number, scope.isActive(number), false);
1755
+ var page = makePage(number, number, paginationCtrl.isActive(number), false);
4350
1756
  scope.pages.push(page);
4351
1757
  }
4352
1758
 
@@ -4365,25 +1771,21 @@ angular.module('ui.bootstrap.pagination', [])
4365
1771
 
4366
1772
  // Add previous & next links
4367
1773
  if (directionLinks) {
4368
- var previousPage = makePage(scope.currentPage - 1, previousText, false, scope.noPrevious());
1774
+ var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, false, paginationCtrl.noPrevious());
4369
1775
  scope.pages.unshift(previousPage);
4370
1776
 
4371
- var nextPage = makePage(scope.currentPage + 1, nextText, false, scope.noNext());
1777
+ var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, false, paginationCtrl.noNext());
4372
1778
  scope.pages.push(nextPage);
4373
1779
  }
4374
1780
 
4375
1781
  // Add first & last links
4376
1782
  if (boundaryLinks) {
4377
- var firstPage = makePage(1, firstText, false, scope.noPrevious());
1783
+ var firstPage = makePage(1, firstText, false, paginationCtrl.noPrevious());
4378
1784
  scope.pages.unshift(firstPage);
4379
1785
 
4380
- var lastPage = makePage(scope.numPages, lastText, false, scope.noNext());
1786
+ var lastPage = makePage(scope.numPages, lastText, false, paginationCtrl.noNext());
4381
1787
  scope.pages.push(lastPage);
4382
1788
  }
4383
-
4384
- if ( scope.currentPage > scope.numPages ) {
4385
- scope.selectPage(scope.numPages);
4386
- }
4387
1789
  });
4388
1790
  }
4389
1791
  };
@@ -4409,9 +1811,9 @@ angular.module('ui.bootstrap.pagination', [])
4409
1811
  link: function(scope, element, attrs, paginationCtrl) {
4410
1812
 
4411
1813
  // Setup configuration parameters
4412
- var previousText = angular.isDefined(attrs.previousText) ? scope.$parent.$eval(attrs.previousText) : config.previousText;
4413
- var nextText = angular.isDefined(attrs.nextText) ? scope.$parent.$eval(attrs.nextText) : config.nextText;
4414
- var align = angular.isDefined(attrs.align) ? scope.$parent.$eval(attrs.align) : config.align;
1814
+ var previousText = paginationCtrl.getAttributeValue(attrs.previousText, config.previousText, true),
1815
+ nextText = paginationCtrl.getAttributeValue(attrs.nextText, config.nextText, true),
1816
+ align = paginationCtrl.getAttributeValue(attrs.align, config.align);
4415
1817
 
4416
1818
  // Create page object used in template
4417
1819
  function makePage(number, text, isDisabled, isPrevious, isNext) {
@@ -4425,117 +1827,19 @@ angular.module('ui.bootstrap.pagination', [])
4425
1827
  }
4426
1828
 
4427
1829
  scope.$watch('numPages + currentPage', function() {
4428
- scope.pages = [];
1830
+ paginationCtrl.reset();
4429
1831
 
4430
1832
  // Add previous & next links
4431
- var previousPage = makePage(scope.currentPage - 1, previousText, scope.noPrevious(), true, false);
1833
+ var previousPage = makePage(paginationCtrl.currentPage - 1, previousText, paginationCtrl.noPrevious(), true, false);
4432
1834
  scope.pages.unshift(previousPage);
4433
1835
 
4434
- var nextPage = makePage(scope.currentPage + 1, nextText, scope.noNext(), false, true);
1836
+ var nextPage = makePage(paginationCtrl.currentPage + 1, nextText, paginationCtrl.noNext(), false, true);
4435
1837
  scope.pages.push(nextPage);
4436
-
4437
- if ( scope.currentPage > scope.numPages ) {
4438
- scope.selectPage(scope.numPages);
4439
- }
4440
1838
  });
4441
1839
  }
4442
1840
  };
4443
1841
  }]);
4444
1842
 
4445
- angular.module('ui.bootstrap.position', [])
4446
-
4447
- /**
4448
- * A set of utility methods that can be use to retrieve position of DOM elements.
4449
- * It is meant to be used where we need to absolute-position DOM elements in
4450
- * relation to other, existing elements (this is the case for tooltips, popovers,
4451
- * typeahead suggestions etc.).
4452
- */
4453
- .factory('$position', ['$document', '$window', function ($document, $window) {
4454
-
4455
- var mouseX, mouseY;
4456
-
4457
- $document.bind('mousemove', function mouseMoved(event) {
4458
- mouseX = event.pageX;
4459
- mouseY = event.pageY;
4460
- });
4461
-
4462
- function getStyle(el, cssprop) {
4463
- if (el.currentStyle) { //IE
4464
- return el.currentStyle[cssprop];
4465
- } else if ($window.getComputedStyle) {
4466
- return $window.getComputedStyle(el)[cssprop];
4467
- }
4468
- // finally try and get inline style
4469
- return el.style[cssprop];
4470
- }
4471
-
4472
- /**
4473
- * Checks if a given element is statically positioned
4474
- * @param element - raw DOM element
4475
- */
4476
- function isStaticPositioned(element) {
4477
- return (getStyle(element, "position") || 'static' ) === 'static';
4478
- }
4479
-
4480
- /**
4481
- * returns the closest, non-statically positioned parentOffset of a given element
4482
- * @param element
4483
- */
4484
- var parentOffsetEl = function (element) {
4485
- var docDomEl = $document[0];
4486
- var offsetParent = element.offsetParent || docDomEl;
4487
- while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
4488
- offsetParent = offsetParent.offsetParent;
4489
- }
4490
- return offsetParent || docDomEl;
4491
- };
4492
-
4493
- return {
4494
- /**
4495
- * Provides read-only equivalent of jQuery's position function:
4496
- * http://api.jquery.com/position/
4497
- */
4498
- position: function (element) {
4499
- var elBCR = this.offset(element);
4500
- var offsetParentBCR = { top: 0, left: 0 };
4501
- var offsetParentEl = parentOffsetEl(element[0]);
4502
- if (offsetParentEl != $document[0]) {
4503
- offsetParentBCR = this.offset(angular.element(offsetParentEl));
4504
- offsetParentBCR.top += offsetParentEl.clientTop;
4505
- offsetParentBCR.left += offsetParentEl.clientLeft;
4506
- }
4507
-
4508
- return {
4509
- width: element.prop('offsetWidth'),
4510
- height: element.prop('offsetHeight'),
4511
- top: elBCR.top - offsetParentBCR.top,
4512
- left: elBCR.left - offsetParentBCR.left
4513
- };
4514
- },
4515
-
4516
- /**
4517
- * Provides read-only equivalent of jQuery's offset function:
4518
- * http://api.jquery.com/offset/
4519
- */
4520
- offset: function (element) {
4521
- var boundingClientRect = element[0].getBoundingClientRect();
4522
- return {
4523
- width: element.prop('offsetWidth'),
4524
- height: element.prop('offsetHeight'),
4525
- top: boundingClientRect.top + ($window.pageYOffset || $document[0].body.scrollTop),
4526
- left: boundingClientRect.left + ($window.pageXOffset || $document[0].body.scrollLeft)
4527
- };
4528
- },
4529
-
4530
- /**
4531
- * Provides the coordinates of the mouse
4532
- */
4533
- mouse: function () {
4534
- return {x: mouseX, y: mouseY};
4535
- }
4536
- };
4537
- }]);
4538
-
4539
1843
  /**
4540
1844
  * The following features are still outstanding: animation as a
4541
1845
  * function, placement as a function, inside, support for more triggers than
@@ -4564,7 +1868,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4564
1868
 
4565
1869
  // The options specified to the provider globally.
4566
1870
  var globalOptions = {};
4567
-
1871
+
4568
1872
  /**
4569
1873
  * `options({})` allows global configuration of all tooltips in the
4570
1874
  * application.
@@ -4574,9 +1878,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4574
1878
  * $tooltipProvider.options( { placement: 'left' } );
4575
1879
  * });
4576
1880
  */
4577
- this.options = function( value ) {
4578
- angular.extend( globalOptions, value );
4579
- };
1881
+ this.options = function( value ) {
1882
+ angular.extend( globalOptions, value );
1883
+ };
4580
1884
 
4581
1885
  /**
4582
1886
  * This allows you to extend the set of trigger mappings available. E.g.:
@@ -4620,16 +1924,9 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4620
1924
  * undefined; otherwise, it uses the `triggerMap` value of the show
4621
1925
  * trigger; else it will just use the show trigger.
4622
1926
  */
4623
- function setTriggers ( trigger ) {
4624
- var show, hide;
4625
-
4626
- show = trigger || options.trigger || defaultTriggerShow;
4627
- if ( angular.isDefined ( options.trigger ) ) {
4628
- hide = triggerMap[options.trigger] || show;
4629
- } else {
4630
- hide = triggerMap[show] || show;
4631
- }
4632
-
1927
+ function getTriggers ( trigger ) {
1928
+ var show = trigger || options.trigger || defaultTriggerShow;
1929
+ var hide = triggerMap[show] || show;
4633
1930
  return {
4634
1931
  show: show,
4635
1932
  hide: hide
@@ -4637,11 +1934,10 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4637
1934
  }
4638
1935
 
4639
1936
  var directiveName = snake_case( type );
4640
- var triggers = setTriggers( undefined );
4641
1937
 
4642
1938
  var startSym = $interpolate.startSymbol();
4643
1939
  var endSym = $interpolate.endSymbol();
4644
- var template =
1940
+ var template =
4645
1941
  '<'+ directiveName +'-popup '+
4646
1942
  'title="'+startSym+'tt_title'+endSym+'" '+
4647
1943
  'content="'+startSym+'tt_content'+endSym+'" '+
@@ -4660,6 +1956,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4660
1956
  var popupTimeout;
4661
1957
  var $body;
4662
1958
  var appendToBody = angular.isDefined( options.appendToBody ) ? options.appendToBody : false;
1959
+ var triggers = getTriggers( undefined );
1960
+ var hasRegisteredTriggers = false;
4663
1961
 
4664
1962
  // By default, the tooltip is not open.
4665
1963
  // TODO add ability to start tooltip opened
@@ -4672,7 +1970,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4672
1970
  hideTooltipBind();
4673
1971
  }
4674
1972
  }
4675
-
1973
+
4676
1974
  // Show the tooltip with delay if specified, otherwise show it immediately
4677
1975
  function showTooltipBind() {
4678
1976
  if ( scope.tt_popupDelay ) {
@@ -4687,7 +1985,7 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4687
1985
  hide();
4688
1986
  });
4689
1987
  }
4690
-
1988
+
4691
1989
  // Show the tooltip popup element.
4692
1990
  function show() {
4693
1991
  var position,
@@ -4705,11 +2003,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4705
2003
  if ( transitionTimeout ) {
4706
2004
  $timeout.cancel( transitionTimeout );
4707
2005
  }
4708
-
2006
+
4709
2007
  // Set the initial positioning.
4710
2008
  tooltip.css({ top: 0, left: 0, display: 'block' });
4711
-
4712
- // Now we add it to the DOM because need some info about it. But it's not
2009
+
2010
+ // Now we add it to the DOM because need some info about it. But it's not
4713
2011
  // visible yet anyway.
4714
2012
  if ( appendToBody ) {
4715
2013
  $body = $body || $document.find( 'body' );
@@ -4719,12 +2017,12 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4719
2017
  }
4720
2018
 
4721
2019
  // Get the position of the directive element.
4722
- position = options.appendToBody ? $position.offset( element ) : $position.position( element );
2020
+ position = appendToBody ? $position.offset( element ) : $position.position( element );
4723
2021
 
4724
2022
  // Get the height and width of the tooltip so we can center it.
4725
2023
  ttWidth = tooltip.prop( 'offsetWidth' );
4726
2024
  ttHeight = tooltip.prop( 'offsetHeight' );
4727
-
2025
+
4728
2026
  // Calculate the tooltip's top and left coordinates to center it with
4729
2027
  // this directive.
4730
2028
  switch ( scope.tt_placement ) {
@@ -4766,11 +2064,11 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4766
2064
 
4767
2065
  // Now set the calculated positioning.
4768
2066
  tooltip.css( ttPosition );
4769
-
2067
+
4770
2068
  // And show the tooltip.
4771
2069
  scope.tt_isOpen = true;
4772
2070
  }
4773
-
2071
+
4774
2072
  // Hide the tooltip popup element.
4775
2073
  function hide() {
4776
2074
  // First things first: we don't show it anymore.
@@ -4778,8 +2076,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4778
2076
 
4779
2077
  //if tooltip is going to be shown after delay, we must cancel this
4780
2078
  $timeout.cancel( popupTimeout );
4781
-
4782
- // And now we remove it from the DOM. However, if we have animation, we
2079
+
2080
+ // And now we remove it from the DOM. However, if we have animation, we
4783
2081
  // need to wait for it to expire beforehand.
4784
2082
  // FIXME: this is a placeholder for a port of the transitions library.
4785
2083
  if ( angular.isDefined( scope.tt_animation ) && scope.tt_animation() ) {
@@ -4814,10 +2112,13 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4814
2112
  });
4815
2113
 
4816
2114
  attrs.$observe( prefix+'Trigger', function ( val ) {
4817
- element.unbind( triggers.show );
4818
- element.unbind( triggers.hide );
4819
2115
 
4820
- triggers = setTriggers( val );
2116
+ if (hasRegisteredTriggers) {
2117
+ element.unbind( triggers.show, showTooltipBind );
2118
+ element.unbind( triggers.hide, hideTooltipBind );
2119
+ }
2120
+
2121
+ triggers = getTriggers( val );
4821
2122
 
4822
2123
  if ( triggers.show === triggers.hide ) {
4823
2124
  element.bind( triggers.show, toggleTooltipBind );
@@ -4825,6 +2126,8 @@ angular.module( 'ui.bootstrap.tooltip', [ 'ui.bootstrap.position' ] )
4825
2126
  element.bind( triggers.show, showTooltipBind );
4826
2127
  element.bind( triggers.hide, hideTooltipBind );
4827
2128
  }
2129
+
2130
+ hasRegisteredTriggers = true;
4828
2131
  });
4829
2132
 
4830
2133
  attrs.$observe( prefix+'AppendToBody', function ( val ) {
@@ -5017,13 +2320,15 @@ angular.module('ui.bootstrap.rating', [])
5017
2320
  return {
5018
2321
  restrict: 'EA',
5019
2322
  scope: {
5020
- value: '='
2323
+ value: '=',
2324
+ onHover: '&',
2325
+ onLeave: '&'
5021
2326
  },
5022
2327
  templateUrl: 'template/rating/rating.html',
5023
2328
  replace: true,
5024
2329
  link: function(scope, element, attrs) {
5025
2330
 
5026
- var maxRange = angular.isDefined(attrs.max) ? scope.$eval(attrs.max) : ratingConfig.max;
2331
+ var maxRange = angular.isDefined(attrs.max) ? scope.$parent.$eval(attrs.max) : ratingConfig.max;
5027
2332
 
5028
2333
  scope.range = [];
5029
2334
  for (var i = 1; i <= maxRange; i++) {
@@ -5040,10 +2345,12 @@ angular.module('ui.bootstrap.rating', [])
5040
2345
  if ( ! scope.readonly ) {
5041
2346
  scope.val = value;
5042
2347
  }
2348
+ scope.onHover({value: value});
5043
2349
  };
5044
2350
 
5045
2351
  scope.reset = function() {
5046
2352
  scope.val = angular.copy(scope.value);
2353
+ scope.onLeave();
5047
2354
  };
5048
2355
  scope.reset();
5049
2356
 
@@ -5079,6 +2386,7 @@ angular.module('ui.bootstrap.tabs', [])
5079
2386
 
5080
2387
  .controller('TabsetController', ['$scope', '$element',
5081
2388
  function TabsetCtrl($scope, $element) {
2389
+
5082
2390
  var ctrl = this,
5083
2391
  tabs = ctrl.tabs = $scope.tabs = [];
5084
2392
 
@@ -5091,7 +2399,7 @@ function TabsetCtrl($scope, $element) {
5091
2399
 
5092
2400
  ctrl.addTab = function addTab(tab) {
5093
2401
  tabs.push(tab);
5094
- if (tabs.length == 1) {
2402
+ if (tabs.length === 1 || tab.active) {
5095
2403
  ctrl.select(tab);
5096
2404
  }
5097
2405
  };
@@ -5117,6 +2425,8 @@ function TabsetCtrl($scope, $element) {
5117
2425
  * Tabset is the outer container for the tabs directive
5118
2426
  *
5119
2427
  * @param {boolean=} vertical Whether or not to use vertical styling for the tabs.
2428
+ * @param {string=} direction What direction the tabs should be rendered. Available:
2429
+ * 'right', 'left', 'below'.
5120
2430
  *
5121
2431
  * @example
5122
2432
  <example module="ui.bootstrap">
@@ -5137,12 +2447,20 @@ function TabsetCtrl($scope, $element) {
5137
2447
  return {
5138
2448
  restrict: 'EA',
5139
2449
  transclude: true,
2450
+ replace: true,
2451
+ require: '^tabset',
5140
2452
  scope: {},
5141
2453
  controller: 'TabsetController',
5142
2454
  templateUrl: 'template/tabs/tabset.html',
5143
- link: function(scope, element, attrs) {
5144
- scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
5145
- scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2455
+ compile: function(elm, attrs, transclude) {
2456
+ return function(scope, element, attrs, tabsetCtrl) {
2457
+ scope.vertical = angular.isDefined(attrs.vertical) ? scope.$eval(attrs.vertical) : false;
2458
+ scope.type = angular.isDefined(attrs.type) ? scope.$parent.$eval(attrs.type) : 'tabs';
2459
+ scope.direction = angular.isDefined(attrs.direction) ? scope.$parent.$eval(attrs.direction) : 'top';
2460
+ scope.tabsAbove = (scope.direction != 'below');
2461
+ tabsetCtrl.$scope = scope;
2462
+ tabsetCtrl.$transcludeFn = transclude;
2463
+ };
5146
2464
  }
5147
2465
  };
5148
2466
  })
@@ -5237,8 +2555,9 @@ function($parse, $http, $templateCache, $compile) {
5237
2555
  transclude: true,
5238
2556
  scope: {
5239
2557
  heading: '@',
5240
- onSelect: '&select' //This callback is called in contentHeadingTransclude
2558
+ onSelect: '&select', //This callback is called in contentHeadingTransclude
5241
2559
  //once it inserts the tab's content into the dom
2560
+ onDeselect: '&deselect'
5242
2561
  },
5243
2562
  controller: function() {
5244
2563
  //Empty controller so other directives can require being 'under' a tab
@@ -5246,17 +2565,13 @@ function($parse, $http, $templateCache, $compile) {
5246
2565
  compile: function(elm, attrs, transclude) {
5247
2566
  return function postLink(scope, elm, attrs, tabsetCtrl) {
5248
2567
  var getActive, setActive;
5249
- scope.active = false; // default value
5250
2568
  if (attrs.active) {
5251
2569
  getActive = $parse(attrs.active);
5252
2570
  setActive = getActive.assign;
5253
2571
  scope.$parent.$watch(getActive, function updateActive(value) {
5254
- if ( !!value && scope.disabled ) {
5255
- setActive(scope.$parent, false); // Prevent active assignment
5256
- } else {
5257
- scope.active = !!value;
5258
- }
2572
+ scope.active = !!value;
5259
2573
  });
2574
+ scope.active = getActive(scope.$parent);
5260
2575
  } else {
5261
2576
  setActive = getActive = angular.noop;
5262
2577
  }
@@ -5266,6 +2581,8 @@ function($parse, $http, $templateCache, $compile) {
5266
2581
  if (active) {
5267
2582
  tabsetCtrl.select(scope);
5268
2583
  scope.onSelect();
2584
+ } else {
2585
+ scope.onDeselect();
5269
2586
  }
5270
2587
  });
5271
2588
 
@@ -5286,42 +2603,14 @@ function($parse, $http, $templateCache, $compile) {
5286
2603
  scope.$on('$destroy', function() {
5287
2604
  tabsetCtrl.removeTab(scope);
5288
2605
  });
5289
- //If the tabset sets this tab to active, set the parent scope's active
5290
- //binding too. We do this so the watch for the parent's initial active
5291
- //value won't overwrite what is initially set by the tabset
5292
2606
  if (scope.active) {
5293
2607
  setActive(scope.$parent, true);
5294
2608
  }
5295
2609
 
5296
- //Transclude the collection of sibling elements. Use forEach to find
5297
- //the heading if it exists. We don't use a directive for tab-heading
5298
- //because it is problematic. Discussion @ http://git.io/MSNPwQ
5299
- transclude(scope.$parent, function(clone) {
5300
- //Look at every element in the clone collection. If it's tab-heading,
5301
- //mark it as that. If it's not tab-heading, mark it as tab contents
5302
- var contents = [], heading;
5303
- angular.forEach(clone, function(el) {
5304
- //See if it's a tab-heading attr or element directive
5305
- //First make sure it's a normal element, one that has a tagName
5306
- if (el.tagName &&
5307
- (el.hasAttribute("tab-heading") ||
5308
- el.hasAttribute("data-tab-heading") ||
5309
- el.tagName.toLowerCase() == "tab-heading" ||
5310
- el.tagName.toLowerCase() == "data-tab-heading"
5311
- )) {
5312
- heading = el;
5313
- } else {
5314
- contents.push(el);
5315
- }
5316
- });
5317
- //Share what we found on the scope, so our tabHeadingTransclude and
5318
- //tabContentTransclude directives can find out what the heading and
5319
- //contents are.
5320
- if (heading) {
5321
- scope.headingElement = angular.element(heading);
5322
- }
5323
- scope.contentElement = angular.element(contents);
5324
- });
2610
+
2611
+ //We need to transclude later, once the content container is ready.
2612
+ //when this link happens, we're inside a tab heading.
2613
+ scope.$transcludeFn = transclude;
5325
2614
  };
5326
2615
  }
5327
2616
  };
@@ -5342,21 +2631,56 @@ function($parse, $http, $templateCache, $compile) {
5342
2631
  };
5343
2632
  }])
5344
2633
 
5345
- .directive('tabContentTransclude', ['$parse', function($parse) {
2634
+ .directive('tabContentTransclude', ['$compile', '$parse', function($compile, $parse) {
5346
2635
  return {
5347
2636
  restrict: 'A',
5348
2637
  require: '^tabset',
5349
- link: function(scope, elm, attrs, tabsetCtrl) {
5350
- scope.$watch($parse(attrs.tabContentTransclude), function(tab) {
5351
- elm.html('');
5352
- if (tab) {
5353
- elm.append(tab.contentElement);
5354
- }
2638
+ link: function(scope, elm, attrs) {
2639
+ var tab = scope.$eval(attrs.tabContentTransclude);
2640
+
2641
+ //Now our tab is ready to be transcluded: both the tab heading area
2642
+ //and the tab content area are loaded. Transclude 'em both.
2643
+ tab.$transcludeFn(tab.$parent, function(contents) {
2644
+ angular.forEach(contents, function(node) {
2645
+ if (isTabHeading(node)) {
2646
+ //Let tabHeadingTransclude know.
2647
+ tab.headingElement = node;
2648
+ } else {
2649
+ elm.append(node);
2650
+ }
2651
+ });
5355
2652
  });
5356
2653
  }
5357
2654
  };
2655
+ function isTabHeading(node) {
2656
+ return node.tagName && (
2657
+ node.hasAttribute('tab-heading') ||
2658
+ node.hasAttribute('data-tab-heading') ||
2659
+ node.tagName.toLowerCase() === 'tab-heading' ||
2660
+ node.tagName.toLowerCase() === 'data-tab-heading'
2661
+ );
2662
+ }
5358
2663
  }])
5359
2664
 
2665
+ .directive('tabsetTitles', function($http) {
2666
+ return {
2667
+ restrict: 'A',
2668
+ require: '^tabset',
2669
+ templateUrl: 'template/tabs/tabset-titles.html',
2670
+ replace: true,
2671
+ link: function(scope, elm, attrs, tabsetCtrl) {
2672
+ if (!scope.$eval(attrs.tabsetTitles)) {
2673
+ elm.remove();
2674
+ } else {
2675
+ //now that tabs location has been decided, transclude the tab titles in
2676
+ tabsetCtrl.$transcludeFn(tabsetCtrl.$scope.$parent, function(node) {
2677
+ elm.append(node);
2678
+ });
2679
+ }
2680
+ }
2681
+ };
2682
+ })
2683
+
5360
2684
  ;
5361
2685
 
5362
2686
 
@@ -5452,20 +2776,22 @@ angular.module('ui.bootstrap.timepicker', [])
5452
2776
  // Respond on mousewheel spin
5453
2777
  var mousewheel = (angular.isDefined(attrs.mousewheel)) ? scope.$eval(attrs.mousewheel) : timepickerConfig.mousewheel;
5454
2778
  if ( mousewheel ) {
5455
-
2779
+
5456
2780
  var isScrollingUp = function(e) {
5457
2781
  if (e.originalEvent) {
5458
2782
  e = e.originalEvent;
5459
2783
  }
5460
- return (e.detail || e.wheelDelta > 0);
2784
+ //pick correct delta variable depending on event
2785
+ var delta = (e.wheelDelta) ? e.wheelDelta : -e.deltaY;
2786
+ return (e.detail || delta > 0);
5461
2787
  };
5462
-
5463
- hoursInputEl.bind('mousewheel', function(e) {
2788
+
2789
+ hoursInputEl.bind('mousewheel wheel', function(e) {
5464
2790
  scope.$apply( (isScrollingUp(e)) ? scope.incrementHours() : scope.decrementHours() );
5465
2791
  e.preventDefault();
5466
2792
  });
5467
2793
 
5468
- minutesInputEl.bind('mousewheel', function(e) {
2794
+ minutesInputEl.bind('mousewheel wheel', function(e) {
5469
2795
  scope.$apply( (isScrollingUp(e)) ? scope.incrementMinutes() : scope.decrementMinutes() );
5470
2796
  e.preventDefault();
5471
2797
  });
@@ -5552,10 +2878,8 @@ angular.module('ui.bootstrap.timepicker', [])
5552
2878
 
5553
2879
  function addMinutes( minutes ) {
5554
2880
  var dt = new Date( selected.getTime() + minutes * 60000 );
5555
- if ( dt.getDate() !== selected.getDate()) {
5556
- dt.setDate( dt.getDate() - 1 );
5557
- }
5558
- selected.setTime( dt.getTime() );
2881
+ selected.setHours( dt.getHours() );
2882
+ selected.setMinutes( dt.getMinutes() );
5559
2883
  scope.model = new Date( selected );
5560
2884
  }
5561
2885
 
@@ -5577,6 +2901,7 @@ angular.module('ui.bootstrap.timepicker', [])
5577
2901
  }
5578
2902
  };
5579
2903
  }]);
2904
+
5580
2905
  angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5581
2906
 
5582
2907
  /**
@@ -5616,7 +2941,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5616
2941
  require:'ngModel',
5617
2942
  link:function (originalScope, element, attrs, modelCtrl) {
5618
2943
 
5619
- var selected;
2944
+ //SUPPORTED ATTRIBUTES (OPTIONS)
5620
2945
 
5621
2946
  //minimal no of characters that needs to be entered before typeahead kicks-in
5622
2947
  var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
@@ -5624,16 +2949,26 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5624
2949
  //minimal wait time after last character typed before typehead kicks-in
5625
2950
  var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
5626
2951
 
5627
- //expressions used by typeahead
5628
- var parserResult = typeaheadParser.parse(attrs.typeahead);
5629
-
5630
2952
  //should it restrict model values to the ones selected from the popup only?
5631
2953
  var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
5632
2954
 
2955
+ //binding to a variable that indicates if matches are being retrieved asynchronously
5633
2956
  var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
5634
2957
 
2958
+ //a callback executed when a match is selected
5635
2959
  var onSelectCallback = $parse(attrs.typeaheadOnSelect);
5636
2960
 
2961
+ var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
2962
+
2963
+ //INTERNAL VARIABLES
2964
+
2965
+ //model setter executed upon match selection
2966
+ var $setModelValue = $parse(attrs.ngModel).assign;
2967
+
2968
+ //expressions used by typeahead
2969
+ var parserResult = typeaheadParser.parse(attrs.typeahead);
2970
+
2971
+
5637
2972
  //pop-up element used to display matches
5638
2973
  var popUpEl = angular.element('<typeahead-popup></typeahead-popup>');
5639
2974
  popUpEl.attr({
@@ -5643,6 +2978,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5643
2978
  query: 'query',
5644
2979
  position: 'position'
5645
2980
  });
2981
+ //custom item template
2982
+ if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
2983
+ popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
2984
+ }
5646
2985
 
5647
2986
  //create a child scope for the typeahead directive so we are not polluting original scope
5648
2987
  //with typeahead-specific data (matches, query etc.)
@@ -5702,55 +3041,69 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5702
3041
  //we need to propagate user's query so we can higlight matches
5703
3042
  scope.query = undefined;
5704
3043
 
3044
+ //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
3045
+ var timeoutPromise;
3046
+
5705
3047
  //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
5706
3048
  //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
5707
3049
  modelCtrl.$parsers.push(function (inputValue) {
5708
3050
 
5709
- var timeoutId;
5710
-
5711
3051
  resetMatches();
5712
- if (selected) {
5713
- return inputValue;
5714
- } else {
5715
- if (inputValue && inputValue.length >= minSearch) {
5716
- if (waitTime > 0) {
5717
- if (timeoutId) {
5718
- $timeout.cancel(timeoutId);//cancel previous timeout
5719
- }
5720
- timeoutId = $timeout(function () {
5721
- getMatchesAsync(inputValue);
5722
- }, waitTime);
5723
- } else {
5724
- getMatchesAsync(inputValue);
3052
+ if (inputValue && inputValue.length >= minSearch) {
3053
+ if (waitTime > 0) {
3054
+ if (timeoutPromise) {
3055
+ $timeout.cancel(timeoutPromise);//cancel previous timeout
5725
3056
  }
3057
+ timeoutPromise = $timeout(function () {
3058
+ getMatchesAsync(inputValue);
3059
+ }, waitTime);
3060
+ } else {
3061
+ getMatchesAsync(inputValue);
5726
3062
  }
5727
3063
  }
5728
3064
 
5729
3065
  return isEditable ? inputValue : undefined;
5730
3066
  });
5731
3067
 
5732
- modelCtrl.$render = function () {
3068
+ modelCtrl.$formatters.push(function (modelValue) {
3069
+
3070
+ var candidateViewValue, emptyViewValue;
5733
3071
  var locals = {};
5734
- locals[parserResult.itemName] = selected || modelCtrl.$viewValue;
5735
- element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
5736
- selected = undefined;
5737
- };
3072
+
3073
+ if (inputFormatter) {
3074
+
3075
+ locals['$model'] = modelValue;
3076
+ return inputFormatter(originalScope, locals);
3077
+
3078
+ } else {
3079
+ locals[parserResult.itemName] = modelValue;
3080
+
3081
+ //it might happen that we don't have enough info to properly render input value
3082
+ //we need to check for this situation and simply return model value if we can't apply custom formatting
3083
+ candidateViewValue = parserResult.viewMapper(originalScope, locals);
3084
+ emptyViewValue = parserResult.viewMapper(originalScope, {});
3085
+
3086
+ return candidateViewValue!== emptyViewValue ? candidateViewValue : modelValue;
3087
+ }
3088
+ });
5738
3089
 
5739
3090
  scope.select = function (activeIdx) {
5740
3091
  //called from within the $digest() cycle
5741
3092
  var locals = {};
5742
3093
  var model, item;
5743
- locals[parserResult.itemName] = item = selected = scope.matches[activeIdx].model;
5744
3094
 
5745
- model = parserResult.modelMapper(scope, locals);
5746
- modelCtrl.$setViewValue(model);
5747
- modelCtrl.$render();
5748
- onSelectCallback(scope, {
3095
+ locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
3096
+ model = parserResult.modelMapper(originalScope, locals);
3097
+ $setModelValue(originalScope, model);
3098
+
3099
+ onSelectCallback(originalScope, {
5749
3100
  $item: item,
5750
3101
  $model: model,
5751
- $label: parserResult.viewMapper(scope, locals)
3102
+ $label: parserResult.viewMapper(originalScope, locals)
5752
3103
  });
5753
3104
 
3105
+ //return focus to the input element if a mach was selected via a mouse click event
3106
+ resetMatches();
5754
3107
  element[0].focus();
5755
3108
  };
5756
3109
 
@@ -5807,9 +3160,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5807
3160
  select:'&'
5808
3161
  },
5809
3162
  replace:true,
5810
- templateUrl:'template/typeahead/typeahead.html',
3163
+ templateUrl:'template/typeahead/typeahead-popup.html',
5811
3164
  link:function (scope, element, attrs) {
5812
3165
 
3166
+ scope.templateUrl = attrs.templateUrl;
3167
+
5813
3168
  scope.isOpen = function () {
5814
3169
  return scope.matches.length > 0;
5815
3170
  };
@@ -5829,6 +3184,23 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
5829
3184
  };
5830
3185
  })
5831
3186
 
3187
+ .directive('typeaheadMatch', ['$http', '$templateCache', '$compile', '$parse', function ($http, $templateCache, $compile, $parse) {
3188
+ return {
3189
+ restrict:'E',
3190
+ scope:{
3191
+ index:'=',
3192
+ match:'=',
3193
+ query:'='
3194
+ },
3195
+ link:function (scope, element, attrs) {
3196
+ var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'template/typeahead/typeahead-match.html';
3197
+ $http.get(tplUrl, {cache: $templateCache}).success(function(tplContent){
3198
+ element.replaceWith($compile(tplContent.trim())(scope));
3199
+ });
3200
+ }
3201
+ };
3202
+ }])
3203
+
5832
3204
  .filter('typeaheadHighlight', function() {
5833
3205
 
5834
3206
  function escapeRegexp(queryToEscape) {