angular-ui-bootstrap-rails 0.4.0.0 → 0.5.0.0

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