GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

third-party/angularjs-modules-plugins/UI-Bootstrap/ui-bootstrap-1.3.2.js   F
last analyzed

Complexity

Total Complexity 1402
Complexity/F 2.11

Size

Lines of Code 6900
Function Count 666

Duplication

Duplicated Lines 6158
Ratio 89.25 %

Importance

Changes 0
Metric Value
eloc 4149
dl 6158
loc 6900
rs 0.8
c 0
b 0
f 0
wmc 1402
mnd 736
bc 736
fnc 666
bpm 1.1051
cpm 2.1051
noi 18

80 Functions

Rating   Name   Duplication   Size   Complexity  
F ui-bootstrap-1.3.2.js ➔ getTemplatePromise 5 5 30
A ui-bootstrap-1.3.2.js ➔ timerFn 8 8 2
A ui-bootstrap-1.3.2.js ➔ containsHtml 3 3 1
A ui-bootstrap-1.3.2.js ➔ snake_case 7 7 3
A ui-bootstrap-1.3.2.js ➔ findSlideIndex 7 7 3
A ui-bootstrap-1.3.2.js ➔ resolveError 4 4 1
F ui-bootstrap-1.3.2.js ➔ clearBufferedTransitions 5 5 47
A ui-bootstrap-1.3.2.js ➔ toggleTopWindowClass 8 8 2
A ui-bootstrap-1.3.2.js ➔ getDaysInMonth 4 4 3
F ui-bootstrap-1.3.2.js ➔ getStartingYear 3 3 25
A ui-bootstrap-1.3.2.js ➔ getCheckboxValue 3 3 2
A ui-bootstrap-1.3.2.js ➔ collapse 31 31 4
A ui-bootstrap-1.3.2.js ➔ toInt 3 3 1
A ui-bootstrap-1.3.2.js ➔ pad 8 8 3
F ui-bootstrap-1.3.2.js ➔ $tooltip 460 460 101
C ui-bootstrap-1.3.2.js ➔ removeClass 6 6 9
F ui-bootstrap-1.3.2.js ➔ isValid 15 15 26
F ui-bootstrap-1.3.2.js ➔ escapeRegexp 5 5 62
A ui-bootstrap-1.3.2.js ➔ documentClickBind 16 16 4
F ui-bootstrap-1.3.2.js ➔ getHoursFromTemplate 18 18 44
A ui-bootstrap-1.3.2.js ➔ collapseDone 6 6 1
A ui-bootstrap-1.3.2.js ➔ restartTimer 7 7 2
A ui-bootstrap-1.3.2.js ➔ toTimezone 3 3 2
A ui-bootstrap-1.3.2.js ➔ parseDateString 12 12 4
B ui-bootstrap-1.3.2.js ➔ broadcastClosing 3 3 8
A ui-bootstrap-1.3.2.js ➔ resetTimer 6 6 2
F ui-bootstrap-1.3.2.js ➔ updateHeadingElement 0 6 25
F ui-bootstrap-1.3.2.js ➔ isStaticPositioned 3 3 32
A ui-bootstrap-1.3.2.js ➔ modelIsEmpty 5 5 1
A ui-bootstrap-1.3.2.js ➔ addSecondsToSelected 4 4 1
F ui-bootstrap-1.3.2.js ➔ fireRecalculating 8 8 71
F ui-bootstrap-1.3.2.js ➔ addTab 22 22 44
A ui-bootstrap-1.3.2.js ➔ expandDone 6 6 1
A ui-bootstrap-1.3.2.js ➔ backdropIndex 16 16 4
A ui-bootstrap-1.3.2.js ➔ recalculatePosition 4 4 2
C ui-bootstrap-1.3.2.js ➔ keydownListener 40 40 11
A ui-bootstrap-1.3.2.js ➔ keypressListener 10 10 5
A ui-bootstrap-1.3.2.js ➔ removeTab 17 17 5
A ui-bootstrap-1.3.2.js ➔ inputKeydownBind 16 16 5
B ui-bootstrap-1.3.2.js ➔ resolveWithTemplate 3 3 6
A ui-bootstrap-1.3.2.js ➔ getMinutesFromTemplate 8 8 2
B ui-bootstrap-1.3.2.js ➔ goNext 40 40 8
A ui-bootstrap-1.3.2.js ➔ makeValid 6 6 1
F ui-bootstrap-1.3.2.js ➔ refresh 5 5 59
A ui-bootstrap-1.3.2.js ➔ positionPopup 11 11 4
A ui-bootstrap-1.3.2.js ➔ checkRemoveBackdrop 11 11 3
C ui-bootstrap-1.3.2.js ➔ resolveSuccess 67 67 8
B ui-bootstrap-1.3.2.js ➔ expand 25 25 8
B ui-bootstrap-1.3.2.js ➔ parseDate 23 23 7
B ui-bootstrap-1.3.2.js ➔ validator 25 25 6
C ui-bootstrap-1.3.2.js ➔ setTriggers 3 3 9
A ui-bootstrap-1.3.2.js ➔ resetTransition 6 6 2
F ui-bootstrap-1.3.2.js ➔ linkFn 13 13 97
C ui-bootstrap-1.3.2.js ➔ removeAfterAnimate 39 39 9
A ui-bootstrap-1.3.2.js ➔ setActive 5 5 2
A ui-bootstrap-1.3.2.js ➔ getSecondsFromTemplate 4 4 2
F ui-bootstrap-1.3.2.js ➔ getTrueValue 3 3 52
F ui-bootstrap-1.3.2.js ➔ isVisible 5 5 22
F ui-bootstrap-1.3.2.js ➔ createParser 60 60 77
F ui-bootstrap-1.3.2.js ➔ handleDestroyEvent 3 3 36
A ui-bootstrap-1.3.2.js ➔ convertTimezoneToLocal 5 5 2
A ui-bootstrap-1.3.2.js ➔ fromTimezone 3 3 2
D ui-bootstrap-1.3.2.js ➔ getISO8601WeekNumber 8 8 12
A ui-bootstrap-1.3.2.js ➔ removeScope 17 17 4
F ui-bootstrap-1.3.2.js ➔ setMode 4 4 62
F ui-bootstrap-1.3.2.js ➔ cameltoDash 3 3 135
A ui-bootstrap-1.3.2.js ➔ timezoneToOffset 4 4 2
B ui-bootstrap-1.3.2.js ➔ addForExp 35 35 8
A ui-bootstrap-1.3.2.js ➔ getSlideByIndex 7 7 3
C ui-bootstrap-1.3.2.js ➔ updateTemplate 27 27 9
F ui-bootstrap-1.3.2.js ➔ makePage 7 7 43
F ui-bootstrap-1.3.2.js ➔ getMaxOrDefault 3 3 47
A ui-bootstrap-1.3.2.js ➔ addDateMinutes 5 5 1
A ui-bootstrap-1.3.2.js ➔ getFalseValue 3 3 1
B ui-bootstrap-1.3.2.js ➔ findTabIndex 7 7 6
B ui-bootstrap-1.3.2.js ➔ isTabHeading 11 11 6
C ui-bootstrap-1.3.2.js ➔ removeModalWindow 35 35 9
A ui-bootstrap-1.3.2.js ➔ addMinutes 3 3 1
F ui-bootstrap-1.3.2.js ➔ getPages 70 70 14
A ui-bootstrap-1.3.2.js ➔ addSeconds 6 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like third-party/angularjs-modules-plugins/UI-Bootstrap/ui-bootstrap-1.3.2.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*
2
 * angular-ui-bootstrap
3
 * http://angular-ui.github.io/bootstrap/
4
5
 * Version: 1.3.2 - 2016-04-14
6
 * License: MIT
7
 */angular.module("ui.bootstrap", ["ui.bootstrap.collapse","ui.bootstrap.accordion","ui.bootstrap.alert","ui.bootstrap.buttons","ui.bootstrap.carousel","ui.bootstrap.dateparser","ui.bootstrap.isClass","ui.bootstrap.datepicker","ui.bootstrap.position","ui.bootstrap.datepickerPopup","ui.bootstrap.debounce","ui.bootstrap.dropdown","ui.bootstrap.stackedMap","ui.bootstrap.modal","ui.bootstrap.paging","ui.bootstrap.pager","ui.bootstrap.pagination","ui.bootstrap.tooltip","ui.bootstrap.popover","ui.bootstrap.progressbar","ui.bootstrap.rating","ui.bootstrap.tabs","ui.bootstrap.timepicker","ui.bootstrap.typeahead"]);
8
angular.module('ui.bootstrap.collapse', [])
9
10 View Code Duplication
  .directive('uibCollapse', ['$animate', '$q', '$parse', '$injector', function($animate, $q, $parse, $injector) {
11
    var $animateCss = $injector.has('$animateCss') ? $injector.get('$animateCss') : null;
12
    return {
13
      link: function(scope, element, attrs) {
14
        var expandingExpr = $parse(attrs.expanding),
15
            expandedExpr = $parse(attrs.expanded),
16
            collapsingExpr = $parse(attrs.collapsing),
17
            collapsedExpr = $parse(attrs.collapsed);
18
19
        if (!scope.$eval(attrs.uibCollapse)) {
20
          element.addClass('in')
21
            .addClass('collapse')
22
            .attr('aria-expanded', true)
23
            .attr('aria-hidden', false)
24
            .css({height: 'auto'});
25
        }
26
27
        function expand() {
28
          if (element.hasClass('collapse') && element.hasClass('in')) {
29
            return;
30
          }
31
32
          $q.resolve(expandingExpr(scope))
33
            .then(function() {
34
              element.removeClass('collapse')
35
                .addClass('collapsing')
36
                .attr('aria-expanded', true)
37
                .attr('aria-hidden', false);
38
39
              if ($animateCss) {
40
                $animateCss(element, {
41
                  addClass: 'in',
42
                  easing: 'ease',
43
                  to: { height: element[0].scrollHeight + 'px' }
44
                }).start()['finally'](expandDone);
45
              } else {
46
                $animate.addClass(element, 'in', {
47
                  to: { height: element[0].scrollHeight + 'px' }
48
                }).then(expandDone);
49
              }
50
            });
51
        }
52
53
        function expandDone() {
54
          element.removeClass('collapsing')
55
            .addClass('collapse')
56
            .css({height: 'auto'});
57
          expandedExpr(scope);
58
        }
59
60
        function collapse() {
61
          if (!element.hasClass('collapse') && !element.hasClass('in')) {
62
            return collapseDone();
63
          }
64
65
          $q.resolve(collapsingExpr(scope))
66
            .then(function() {
67
              element
68
                // IMPORTANT: The height must be set before adding "collapsing" class.
69
                // Otherwise, the browser attempts to animate from height 0 (in
70
                // collapsing class) to the given height here.
71
                .css({height: element[0].scrollHeight + 'px'})
72
                // initially all panel collapse have the collapse class, this removal
73
                // prevents the animation from jumping to collapsed state
74
                .removeClass('collapse')
75
                .addClass('collapsing')
76
                .attr('aria-expanded', false)
77
                .attr('aria-hidden', true);
78
79
              if ($animateCss) {
80
                $animateCss(element, {
81
                  removeClass: 'in',
82
                  to: {height: '0'}
83
                }).start()['finally'](collapseDone);
84
              } else {
85
                $animate.removeClass(element, 'in', {
86
                  to: {height: '0'}
87
                }).then(collapseDone);
88
              }
89
            });
90
        }
91
92
        function collapseDone() {
93
          element.css({height: '0'}); // Required so that collapse works when animation is disabled
94
          element.removeClass('collapsing')
95
            .addClass('collapse');
96
          collapsedExpr(scope);
97
        }
98
99
        scope.$watch(attrs.uibCollapse, function(shouldCollapse) {
100
          if (shouldCollapse) {
101
            collapse();
102
          } else {
103
            expand();
104
          }
105
        });
106
      }
107
    };
108
  }]);
109
110
angular.module('ui.bootstrap.accordion', ['ui.bootstrap.collapse'])
111
112
.constant('uibAccordionConfig', {
113
  closeOthers: true
114
})
115
116 View Code Duplication
.controller('UibAccordionController', ['$scope', '$attrs', 'uibAccordionConfig', function($scope, $attrs, accordionConfig) {
117
  // This array keeps track of the accordion groups
118
  this.groups = [];
119
120
  // Ensure that all the groups in this accordion are closed, unless close-others explicitly says not to
121
  this.closeOthers = function(openGroup) {
122
    var closeOthers = angular.isDefined($attrs.closeOthers) ?
123
      $scope.$eval($attrs.closeOthers) : accordionConfig.closeOthers;
124
    if (closeOthers) {
125
      angular.forEach(this.groups, function(group) {
126
        if (group !== openGroup) {
127
          group.isOpen = false;
128
        }
129
      });
130
    }
131
  };
132
133
  // This is called from the accordion-group directive to add itself to the accordion
134
  this.addGroup = function(groupScope) {
135
    var that = this;
136
    this.groups.push(groupScope);
137
138
    groupScope.$on('$destroy', function(event) {
139
      that.removeGroup(groupScope);
140
    });
141
  };
142
143
  // This is called from the accordion-group directive when to remove itself
144
  this.removeGroup = function(group) {
145
    var index = this.groups.indexOf(group);
146
    if (index !== -1) {
147
      this.groups.splice(index, 1);
148
    }
149
  };
150
}])
151
152
// The accordion directive simply sets up the directive controller
153
// and adds an accordion CSS class to itself element.
154
.directive('uibAccordion', function() {
155
  return {
156
    controller: 'UibAccordionController',
157
    controllerAs: 'accordion',
158
    transclude: true,
159
    templateUrl: function(element, attrs) {
160
      return attrs.templateUrl || 'uib/template/accordion/accordion.html';
161
    }
162
  };
163
})
164
165
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
166 View Code Duplication
.directive('uibAccordionGroup', function() {
167
  return {
168
    require: '^uibAccordion',         // We need this directive to be inside an accordion
169
    transclude: true,              // It transcludes the contents of the directive into the template
170
    replace: true,                // The element containing the directive will be replaced with the template
171
    templateUrl: function(element, attrs) {
172
      return attrs.templateUrl || 'uib/template/accordion/accordion-group.html';
173
    },
174
    scope: {
175
      heading: '@',               // Interpolate the heading attribute onto this scope
176
      panelClass: '@?',           // Ditto with panelClass
177
      isOpen: '=?',
178
      isDisabled: '=?'
179
    },
180
    controller: function() {
181
      this.setHeading = function(element) {
182
        this.heading = element;
183
      };
184
    },
185
    link: function(scope, element, attrs, accordionCtrl) {
186
      accordionCtrl.addGroup(scope);
187
188
      scope.openClass = attrs.openClass || 'panel-open';
189
      scope.panelClass = attrs.panelClass || 'panel-default';
190
      scope.$watch('isOpen', function(value) {
191
        element.toggleClass(scope.openClass, !!value);
192
        if (value) {
193
          accordionCtrl.closeOthers(scope);
194
        }
195
      });
196
197
      scope.toggleOpen = function($event) {
198
        if (!scope.isDisabled) {
199
          if (!$event || $event.which === 32) {
200
            scope.isOpen = !scope.isOpen;
201
          }
202
        }
203
      };
204
205
      var id = 'accordiongroup-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
206
      scope.headingId = id + '-tab';
207
      scope.panelId = id + '-panel';
208
    }
209
  };
210
})
211
212
// Use accordion-heading below an accordion-group to provide a heading containing HTML
213
.directive('uibAccordionHeading', function() {
214
  return {
215
    transclude: true,   // Grab the contents to be used as the heading
216
    template: '',       // In effect remove this element!
217
    replace: true,
218
    require: '^uibAccordionGroup',
219
    link: function(scope, element, attrs, accordionGroupCtrl, transclude) {
220
      // Pass the heading to the accordion-group controller
221
      // so that it can be transcluded into the right place in the template
222
      // [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
223
      accordionGroupCtrl.setHeading(transclude(scope, angular.noop));
224
    }
225
  };
226
})
227
228
// Use in the accordion-group template to indicate where you want the heading to be transcluded
229
// You must provide the property on the accordion-group controller that will hold the transcluded element
230
.directive('uibAccordionTransclude', function() {
231
  return {
232
    require: '^uibAccordionGroup',
233
    link: function(scope, element, attrs, controller) {
234
      scope.$watch(function() { return controller[attrs.uibAccordionTransclude]; }, function(heading) {
235
        if (heading) {
236
          var elem = angular.element(element[0].querySelector('[uib-accordion-header]'));
237
          elem.html('');
238
          elem.append(heading);
239
        }
240
      });
241
    }
242
  };
243
});
244
245
angular.module('ui.bootstrap.alert', [])
246
247
.controller('UibAlertController', ['$scope', '$attrs', '$interpolate', '$timeout', function($scope, $attrs, $interpolate, $timeout) {
248
  $scope.closeable = !!$attrs.close;
249
250
  var dismissOnTimeout = angular.isDefined($attrs.dismissOnTimeout) ?
251
    $interpolate($attrs.dismissOnTimeout)($scope.$parent) : null;
252
253
  if (dismissOnTimeout) {
254
    $timeout(function() {
255
      $scope.close();
256
    }, parseInt(dismissOnTimeout, 10));
257
  }
258
}])
259
260
.directive('uibAlert', function() {
261
  return {
262
    controller: 'UibAlertController',
263
    controllerAs: 'alert',
264
    templateUrl: function(element, attrs) {
265
      return attrs.templateUrl || 'uib/template/alert/alert.html';
266
    },
267
    transclude: true,
268
    replace: true,
269
    scope: {
270
      type: '@',
271
      close: '&'
272
    }
273
  };
274
});
275
276
angular.module('ui.bootstrap.buttons', [])
277
278
.constant('uibButtonConfig', {
279
  activeClass: 'active',
280
  toggleEvent: 'click'
281
})
282
283
.controller('UibButtonsController', ['uibButtonConfig', function(buttonConfig) {
284
  this.activeClass = buttonConfig.activeClass || 'active';
285
  this.toggleEvent = buttonConfig.toggleEvent || 'click';
286
}])
287
288 View Code Duplication
.directive('uibBtnRadio', ['$parse', function($parse) {
289
  return {
290
    require: ['uibBtnRadio', 'ngModel'],
291
    controller: 'UibButtonsController',
292
    controllerAs: 'buttons',
293
    link: function(scope, element, attrs, ctrls) {
294
      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
295
      var uncheckableExpr = $parse(attrs.uibUncheckable);
296
297
      element.find('input').css({display: 'none'});
298
299
      //model -> UI
300
      ngModelCtrl.$render = function() {
301
        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, scope.$eval(attrs.uibBtnRadio)));
302
      };
303
304
      //ui->model
305
      element.on(buttonsCtrl.toggleEvent, function() {
306
        if (attrs.disabled) {
307
          return;
308
        }
309
310
        var isActive = element.hasClass(buttonsCtrl.activeClass);
311
312
        if (!isActive || angular.isDefined(attrs.uncheckable)) {
313
          scope.$apply(function() {
314
            ngModelCtrl.$setViewValue(isActive ? null : scope.$eval(attrs.uibBtnRadio));
315
            ngModelCtrl.$render();
316
          });
317
        }
318
      });
319
320
      if (attrs.uibUncheckable) {
321
        scope.$watch(uncheckableExpr, function(uncheckable) {
322
          attrs.$set('uncheckable', uncheckable ? '' : undefined);
323
        });
324
      }
325
    }
326
  };
327
}])
328
329 View Code Duplication
.directive('uibBtnCheckbox', function() {
330
  return {
331
    require: ['uibBtnCheckbox', 'ngModel'],
332
    controller: 'UibButtonsController',
333
    controllerAs: 'button',
334
    link: function(scope, element, attrs, ctrls) {
335
      var buttonsCtrl = ctrls[0], ngModelCtrl = ctrls[1];
336
337
      element.find('input').css({display: 'none'});
338
339
      function getTrueValue() {
340
        return getCheckboxValue(attrs.btnCheckboxTrue, true);
341
      }
342
343
      function getFalseValue() {
344
        return getCheckboxValue(attrs.btnCheckboxFalse, false);
345
      }
346
347
      function getCheckboxValue(attribute, defaultValue) {
348
        return angular.isDefined(attribute) ? scope.$eval(attribute) : defaultValue;
349
      }
350
351
      //model -> UI
352
      ngModelCtrl.$render = function() {
353
        element.toggleClass(buttonsCtrl.activeClass, angular.equals(ngModelCtrl.$modelValue, getTrueValue()));
354
      };
355
356
      //ui->model
357
      element.on(buttonsCtrl.toggleEvent, function() {
358
        if (attrs.disabled) {
359
          return;
360
        }
361
362
        scope.$apply(function() {
363
          ngModelCtrl.$setViewValue(element.hasClass(buttonsCtrl.activeClass) ? getFalseValue() : getTrueValue());
364
          ngModelCtrl.$render();
365
        });
366
      });
367
    }
368
  };
369
});
370
371
angular.module('ui.bootstrap.carousel', [])
372
373 View Code Duplication
.controller('UibCarouselController', ['$scope', '$element', '$interval', '$timeout', '$animate', function($scope, $element, $interval, $timeout, $animate) {
374
  var self = this,
375
    slides = self.slides = $scope.slides = [],
376
    SLIDE_DIRECTION = 'uib-slideDirection',
377
    currentIndex = $scope.active,
378
    currentInterval, isPlaying, bufferedTransitions = [];
379
380
  var destroyed = false;
381
382
  self.addSlide = function(slide, element) {
383
    slides.push({
384
      slide: slide,
385
      element: element
386
    });
387
    slides.sort(function(a, b) {
388
      return +a.slide.index - +b.slide.index;
389
    });
390
    //if this is the first slide or the slide is set to active, select it
391
    if (slide.index === $scope.active || slides.length === 1 && !angular.isNumber($scope.active)) {
392
      if ($scope.$currentTransition) {
393
        $scope.$currentTransition = null;
394
      }
395
396
      currentIndex = slide.index;
397
      $scope.active = slide.index;
398
      setActive(currentIndex);
399
      self.select(slides[findSlideIndex(slide)]);
400
      if (slides.length === 1) {
401
        $scope.play();
402
      }
403
    }
404
  };
405
406
  self.getCurrentIndex = function() {
407
    for (var i = 0; i < slides.length; i++) {
408
      if (slides[i].slide.index === currentIndex) {
409
        return i;
410
      }
411
    }
412
  };
413
414
  self.next = $scope.next = function() {
415
    var newIndex = (self.getCurrentIndex() + 1) % slides.length;
416
417
    if (newIndex === 0 && $scope.noWrap()) {
418
      $scope.pause();
419
      return;
420
    }
421
422
    return self.select(slides[newIndex], 'next');
423
  };
424
425
  self.prev = $scope.prev = function() {
426
    var newIndex = self.getCurrentIndex() - 1 < 0 ? slides.length - 1 : self.getCurrentIndex() - 1;
427
428
    if ($scope.noWrap() && newIndex === slides.length - 1) {
429
      $scope.pause();
430
      return;
431
    }
432
433
    return self.select(slides[newIndex], 'prev');
434
  };
435
436
  self.removeSlide = function(slide) {
437
    var index = findSlideIndex(slide);
438
439
    var bufferedIndex = bufferedTransitions.indexOf(slides[index]);
440
    if (bufferedIndex !== -1) {
441
      bufferedTransitions.splice(bufferedIndex, 1);
442
    }
443
444
    //get the index of the slide inside the carousel
445
    slides.splice(index, 1);
446
    if (slides.length > 0 && currentIndex === index) {
447
      if (index >= slides.length) {
448
        currentIndex = slides.length - 1;
449
        $scope.active = currentIndex;
450
        setActive(currentIndex);
451
        self.select(slides[slides.length - 1]);
452
      } else {
453
        currentIndex = index;
454
        $scope.active = currentIndex;
455
        setActive(currentIndex);
456
        self.select(slides[index]);
457
      }
458
    } else if (currentIndex > index) {
459
      currentIndex--;
460
      $scope.active = currentIndex;
461
    }
462
463
    //clean the active value when no more slide
464
    if (slides.length === 0) {
465
      currentIndex = null;
466
      $scope.active = null;
467
      clearBufferedTransitions();
468
    }
469
  };
470
471
  /* direction: "prev" or "next" */
472
  self.select = $scope.select = function(nextSlide, direction) {
473
    var nextIndex = findSlideIndex(nextSlide.slide);
474
    //Decide direction if it's not given
475
    if (direction === undefined) {
476
      direction = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
477
    }
478
    //Prevent this user-triggered transition from occurring if there is already one in progress
479
    if (nextSlide.slide.index !== currentIndex &&
480
      !$scope.$currentTransition) {
481
      goNext(nextSlide.slide, nextIndex, direction);
482
    } else if (nextSlide && nextSlide.slide.index !== currentIndex && $scope.$currentTransition) {
483
      bufferedTransitions.push(slides[nextIndex]);
484
    }
485
  };
486
487
  /* Allow outside people to call indexOf on slides array */
488
  $scope.indexOfSlide = function(slide) {
489
    return +slide.slide.index;
490
  };
491
492
  $scope.isActive = function(slide) {
493
    return $scope.active === slide.slide.index;
494
  };
495
496
  $scope.isPrevDisabled = function() {
497
    return $scope.active === 0 && $scope.noWrap();
498
  };
499
500
  $scope.isNextDisabled = function() {
501
    return $scope.active === slides.length - 1 && $scope.noWrap();
502
  };
503
504
  $scope.pause = function() {
505
    if (!$scope.noPause) {
506
      isPlaying = false;
507
      resetTimer();
508
    }
509
  };
510
511
  $scope.play = function() {
512
    if (!isPlaying) {
513
      isPlaying = true;
514
      restartTimer();
515
    }
516
  };
517
518
  $scope.$on('$destroy', function() {
519
    destroyed = true;
520
    resetTimer();
521
  });
522
523
  $scope.$watch('noTransition', function(noTransition) {
524
    $animate.enabled($element, !noTransition);
525
  });
526
527
  $scope.$watch('interval', restartTimer);
528
529
  $scope.$watchCollection('slides', resetTransition);
530
531
  $scope.$watch('active', function(index) {
532
    if (angular.isNumber(index) && currentIndex !== index) {
533
      for (var i = 0; i < slides.length; i++) {
534
        if (slides[i].slide.index === index) {
535
          index = i;
536
          break;
537
        }
538
      }
539
540
      var slide = slides[index];
541
      if (slide) {
542
        setActive(index);
543
        self.select(slides[index]);
544
        currentIndex = index;
545
      }
546
    }
547
  });
548
549
  function clearBufferedTransitions() {
550
    while (bufferedTransitions.length) {
551
      bufferedTransitions.shift();
552
    }
553
  }
554
555
  function getSlideByIndex(index) {
0 ignored issues
show
introduced by
The function getSlideByIndex does not seem to be used and can be removed.
Loading history...
556
    for (var i = 0, l = slides.length; i < l; ++i) {
557
      if (slides[i].index === index) {
558
        return slides[i];
559
      }
560
    }
561
  }
562
563
  function setActive(index) {
564
    for (var i = 0; i < slides.length; i++) {
565
      slides[i].slide.active = i === index;
566
    }
567
  }
568
569
  function goNext(slide, index, direction) {
570
    if (destroyed) {
571
      return;
572
    }
573
574
    angular.extend(slide, {direction: direction});
575
    angular.extend(slides[currentIndex].slide || {}, {direction: direction});
576
    if ($animate.enabled($element) && !$scope.$currentTransition &&
577
      slides[index].element && self.slides.length > 1) {
578
      slides[index].element.data(SLIDE_DIRECTION, slide.direction);
579
      var currentIdx = self.getCurrentIndex();
580
581
      if (angular.isNumber(currentIdx) && slides[currentIdx].element) {
582
        slides[currentIdx].element.data(SLIDE_DIRECTION, slide.direction);
583
      }
584
585
      $scope.$currentTransition = true;
586
      $animate.on('addClass', slides[index].element, function(element, phase) {
587
        if (phase === 'close') {
588
          $scope.$currentTransition = null;
589
          $animate.off('addClass', element);
590
          if (bufferedTransitions.length) {
591
            var nextSlide = bufferedTransitions.pop().slide;
592
            var nextIndex = nextSlide.index;
593
            var nextDirection = nextIndex > self.getCurrentIndex() ? 'next' : 'prev';
594
            clearBufferedTransitions();
595
596
            goNext(nextSlide, nextIndex, nextDirection);
597
          }
598
        }
599
      });
600
    }
601
602
    $scope.active = slide.index;
603
    currentIndex = slide.index;
604
    setActive(index);
605
606
    //every time you change slides, reset the timer
607
    restartTimer();
608
  }
609
610
  function findSlideIndex(slide) {
611
    for (var i = 0; i < slides.length; i++) {
612
      if (slides[i].slide === slide) {
613
        return i;
614
      }
615
    }
616
  }
617
618
  function resetTimer() {
619
    if (currentInterval) {
620
      $interval.cancel(currentInterval);
621
      currentInterval = null;
622
    }
623
  }
624
625
  function resetTransition(slides) {
626
    if (!slides.length) {
627
      $scope.$currentTransition = null;
628
      clearBufferedTransitions();
629
    }
630
  }
631
632
  function restartTimer() {
633
    resetTimer();
634
    var interval = +$scope.interval;
635
    if (!isNaN(interval) && interval > 0) {
636
      currentInterval = $interval(timerFn, interval);
637
    }
638
  }
639
640
  function timerFn() {
641
    var interval = +$scope.interval;
642
    if (isPlaying && !isNaN(interval) && interval > 0 && slides.length) {
643
      $scope.next();
644
    } else {
645
      $scope.pause();
646
    }
647
  }
648
}])
649
650 View Code Duplication
.directive('uibCarousel', function() {
651
  return {
652
    transclude: true,
653
    replace: true,
654
    controller: 'UibCarouselController',
655
    controllerAs: 'carousel',
656
    templateUrl: function(element, attrs) {
657
      return attrs.templateUrl || 'uib/template/carousel/carousel.html';
658
    },
659
    scope: {
660
      active: '=',
661
      interval: '=',
662
      noTransition: '=',
663
      noPause: '=',
664
      noWrap: '&'
665
    }
666
  };
667
})
668
669
.directive('uibSlide', function() {
670
  return {
671
    require: '^uibCarousel',
672
    transclude: true,
673
    replace: true,
674
    templateUrl: function(element, attrs) {
675
      return attrs.templateUrl || 'uib/template/carousel/slide.html';
676
    },
677
    scope: {
678
      actual: '=?',
679
      index: '=?'
680
    },
681
    link: function (scope, element, attrs, carouselCtrl) {
682
      carouselCtrl.addSlide(scope, element);
683
      //when the scope is destroyed then remove the slide from the current slides array
684
      scope.$on('$destroy', function() {
685
        carouselCtrl.removeSlide(scope);
686
      });
687
    }
688
  };
689
})
690
691
.animation('.item', ['$animateCss',
692 View Code Duplication
function($animateCss) {
693
  var SLIDE_DIRECTION = 'uib-slideDirection';
694
695
  function removeClass(element, className, callback) {
696
    element.removeClass(className);
697
    if (callback) {
698
      callback();
699
    }
700
  }
701
702
  return {
703
    beforeAddClass: function(element, className, done) {
704
      if (className === 'active') {
705
        var stopped = false;
0 ignored issues
show
Unused Code introduced by
The variable stopped seems to be never used. Consider removing it.
Loading history...
706
        var direction = element.data(SLIDE_DIRECTION);
707
        var directionClass = direction === 'next' ? 'left' : 'right';
708
        var removeClassFn = removeClass.bind(this, element,
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function removeClass declared on line 695 does not use this.
Loading history...
709
          directionClass + ' ' + direction, done);
710
        element.addClass(direction);
711
712
        $animateCss(element, {addClass: directionClass})
713
          .start()
714
          .done(removeClassFn);
715
716
        return function() {
717
          stopped = true;
0 ignored issues
show
Unused Code introduced by
The variable stopped seems to be never used. Consider removing it.
Loading history...
718
        };
719
      }
720
      done();
721
    },
722
    beforeRemoveClass: function (element, className, done) {
723
      if (className === 'active') {
724
        var stopped = false;
0 ignored issues
show
Unused Code introduced by
The variable stopped seems to be never used. Consider removing it.
Loading history...
725
        var direction = element.data(SLIDE_DIRECTION);
726
        var directionClass = direction === 'next' ? 'left' : 'right';
727
        var removeClassFn = removeClass.bind(this, element, directionClass, done);
0 ignored issues
show
Unused Code introduced by
The call to bind does not seem necessary since the function removeClass declared on line 695 does not use this.
Loading history...
728
729
        $animateCss(element, {addClass: directionClass})
730
          .start()
731
          .done(removeClassFn);
732
733
        return function() {
734
          stopped = true;
0 ignored issues
show
Unused Code introduced by
The variable stopped seems to be never used. Consider removing it.
Loading history...
735
        };
736
      }
737
      done();
738
    }
739
  };
740
}]);
741
742
angular.module('ui.bootstrap.dateparser', [])
743
744 View Code Duplication
.service('uibDateParser', ['$log', '$locale', 'dateFilter', 'orderByFilter', function($log, $locale, dateFilter, orderByFilter) {
745
  // Pulled from https://github.com/mbostock/d3/blob/master/src/format/requote.js
746
  var SPECIAL_CHARACTERS_REGEXP = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
747
748
  var localeId;
749
  var formatCodeToRegex;
750
751
  this.init = function() {
752
    localeId = $locale.id;
753
754
    this.parsers = {};
755
    this.formatters = {};
756
757
    formatCodeToRegex = [
758
      {
759
        key: 'yyyy',
760
        regex: '\\d{4}',
761
        apply: function(value) { this.year = +value; },
762
        formatter: function(date) {
763
          var _date = new Date();
764
          _date.setFullYear(Math.abs(date.getFullYear()));
765
          return dateFilter(_date, 'yyyy');
766
        }
767
      },
768
      {
769
        key: 'yy',
770
        regex: '\\d{2}',
771
        apply: function(value) { value = +value; this.year = value < 69 ? value + 2000 : value + 1900; },
772
        formatter: function(date) {
773
          var _date = new Date();
774
          _date.setFullYear(Math.abs(date.getFullYear()));
775
          return dateFilter(_date, 'yy');
776
        }
777
      },
778
      {
779
        key: 'y',
780
        regex: '\\d{1,4}',
781
        apply: function(value) { this.year = +value; },
782
        formatter: function(date) {
783
          var _date = new Date();
784
          _date.setFullYear(Math.abs(date.getFullYear()));
785
          return dateFilter(_date, 'y');
786
        }
787
      },
788
      {
789
        key: 'M!',
790
        regex: '0?[1-9]|1[0-2]',
791
        apply: function(value) { this.month = value - 1; },
792
        formatter: function(date) {
793
          var value = date.getMonth();
794
          if (/^[0-9]$/.test(value)) {
795
            return dateFilter(date, 'MM');
796
          }
797
798
          return dateFilter(date, 'M');
799
        }
800
      },
801
      {
802
        key: 'MMMM',
803
        regex: $locale.DATETIME_FORMATS.MONTH.join('|'),
804
        apply: function(value) { this.month = $locale.DATETIME_FORMATS.MONTH.indexOf(value); },
805
        formatter: function(date) { return dateFilter(date, 'MMMM'); }
806
      },
807
      {
808
        key: 'MMM',
809
        regex: $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
810
        apply: function(value) { this.month = $locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value); },
811
        formatter: function(date) { return dateFilter(date, 'MMM'); }
812
      },
813
      {
814
        key: 'MM',
815
        regex: '0[1-9]|1[0-2]',
816
        apply: function(value) { this.month = value - 1; },
817
        formatter: function(date) { return dateFilter(date, 'MM'); }
818
      },
819
      {
820
        key: 'M',
821
        regex: '[1-9]|1[0-2]',
822
        apply: function(value) { this.month = value - 1; },
823
        formatter: function(date) { return dateFilter(date, 'M'); }
824
      },
825
      {
826
        key: 'd!',
827
        regex: '[0-2]?[0-9]{1}|3[0-1]{1}',
828
        apply: function(value) { this.date = +value; },
829
        formatter: function(date) {
830
          var value = date.getDate();
831
          if (/^[1-9]$/.test(value)) {
832
            return dateFilter(date, 'dd');
833
          }
834
835
          return dateFilter(date, 'd');
836
        }
837
      },
838
      {
839
        key: 'dd',
840
        regex: '[0-2][0-9]{1}|3[0-1]{1}',
841
        apply: function(value) { this.date = +value; },
842
        formatter: function(date) { return dateFilter(date, 'dd'); }
843
      },
844
      {
845
        key: 'd',
846
        regex: '[1-2]?[0-9]{1}|3[0-1]{1}',
847
        apply: function(value) { this.date = +value; },
848
        formatter: function(date) { return dateFilter(date, 'd'); }
849
      },
850
      {
851
        key: 'EEEE',
852
        regex: $locale.DATETIME_FORMATS.DAY.join('|'),
853
        formatter: function(date) { return dateFilter(date, 'EEEE'); }
854
      },
855
      {
856
        key: 'EEE',
857
        regex: $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
858
        formatter: function(date) { return dateFilter(date, 'EEE'); }
859
      },
860
      {
861
        key: 'HH',
862
        regex: '(?:0|1)[0-9]|2[0-3]',
863
        apply: function(value) { this.hours = +value; },
864
        formatter: function(date) { return dateFilter(date, 'HH'); }
865
      },
866
      {
867
        key: 'hh',
868
        regex: '0[0-9]|1[0-2]',
869
        apply: function(value) { this.hours = +value; },
870
        formatter: function(date) { return dateFilter(date, 'hh'); }
871
      },
872
      {
873
        key: 'H',
874
        regex: '1?[0-9]|2[0-3]',
875
        apply: function(value) { this.hours = +value; },
876
        formatter: function(date) { return dateFilter(date, 'H'); }
877
      },
878
      {
879
        key: 'h',
880
        regex: '[0-9]|1[0-2]',
881
        apply: function(value) { this.hours = +value; },
882
        formatter: function(date) { return dateFilter(date, 'h'); }
883
      },
884
      {
885
        key: 'mm',
886
        regex: '[0-5][0-9]',
887
        apply: function(value) { this.minutes = +value; },
888
        formatter: function(date) { return dateFilter(date, 'mm'); }
889
      },
890
      {
891
        key: 'm',
892
        regex: '[0-9]|[1-5][0-9]',
893
        apply: function(value) { this.minutes = +value; },
894
        formatter: function(date) { return dateFilter(date, 'm'); }
895
      },
896
      {
897
        key: 'sss',
898
        regex: '[0-9][0-9][0-9]',
899
        apply: function(value) { this.milliseconds = +value; },
900
        formatter: function(date) { return dateFilter(date, 'sss'); }
901
      },
902
      {
903
        key: 'ss',
904
        regex: '[0-5][0-9]',
905
        apply: function(value) { this.seconds = +value; },
906
        formatter: function(date) { return dateFilter(date, 'ss'); }
907
      },
908
      {
909
        key: 's',
910
        regex: '[0-9]|[1-5][0-9]',
911
        apply: function(value) { this.seconds = +value; },
912
        formatter: function(date) { return dateFilter(date, 's'); }
913
      },
914
      {
915
        key: 'a',
916
        regex: $locale.DATETIME_FORMATS.AMPMS.join('|'),
917
        apply: function(value) {
918
          if (this.hours === 12) {
919
            this.hours = 0;
920
          }
921
922
          if (value === 'PM') {
923
            this.hours += 12;
924
          }
925
        },
926
        formatter: function(date) { return dateFilter(date, 'a'); }
927
      },
928
      {
929
        key: 'Z',
930
        regex: '[+-]\\d{4}',
931
        apply: function(value) {
932
          var matches = value.match(/([+-])(\d{2})(\d{2})/),
933
            sign = matches[1],
934
            hours = matches[2],
935
            minutes = matches[3];
936
          this.hours += toInt(sign + hours);
937
          this.minutes += toInt(sign + minutes);
938
        },
939
        formatter: function(date) {
940
          return dateFilter(date, 'Z');
941
        }
942
      },
943
      {
944
        key: 'ww',
945
        regex: '[0-4][0-9]|5[0-3]',
946
        formatter: function(date) { return dateFilter(date, 'ww'); }
947
      },
948
      {
949
        key: 'w',
950
        regex: '[0-9]|[1-4][0-9]|5[0-3]',
951
        formatter: function(date) { return dateFilter(date, 'w'); }
952
      },
953
      {
954
        key: 'GGGG',
955
        regex: $locale.DATETIME_FORMATS.ERANAMES.join('|').replace(/\s/g, '\\s'),
956
        formatter: function(date) { return dateFilter(date, 'GGGG'); }
957
      },
958
      {
959
        key: 'GGG',
960
        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
961
        formatter: function(date) { return dateFilter(date, 'GGG'); }
962
      },
963
      {
964
        key: 'GG',
965
        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
966
        formatter: function(date) { return dateFilter(date, 'GG'); }
967
      },
968
      {
969
        key: 'G',
970
        regex: $locale.DATETIME_FORMATS.ERAS.join('|'),
971
        formatter: function(date) { return dateFilter(date, 'G'); }
972
      }
973
    ];
974
  };
975
976
  this.init();
977
978
  function createParser(format, func) {
979
    var map = [], regex = format.split('');
980
981
    // check for literal values
982
    var quoteIndex = format.indexOf('\'');
983
    if (quoteIndex > -1) {
984
      var inLiteral = false;
985
      format = format.split('');
986
      for (var i = quoteIndex; i < format.length; i++) {
987
        if (inLiteral) {
988
          if (format[i] === '\'') {
989
            if (i + 1 < format.length && format[i+1] === '\'') { // escaped single quote
990
              format[i+1] = '$';
991
              regex[i+1] = '';
992
            } else { // end of literal
993
              regex[i] = '';
994
              inLiteral = false;
995
            }
996
          }
997
          format[i] = '$';
998
        } else {
999
          if (format[i] === '\'') { // start of literal
1000
            format[i] = '$';
1001
            regex[i] = '';
1002
            inLiteral = true;
1003
          }
1004
        }
1005
      }
1006
1007
      format = format.join('');
1008
    }
1009
1010
    angular.forEach(formatCodeToRegex, function(data) {
1011
      var index = format.indexOf(data.key);
1012
1013
      if (index > -1) {
1014
        format = format.split('');
1015
1016
        regex[index] = '(' + data.regex + ')';
1017
        format[index] = '$'; // Custom symbol to define consumed part of format
1018
        for (var i = index + 1, n = index + data.key.length; i < n; i++) {
1019
          regex[i] = '';
1020
          format[i] = '$';
1021
        }
1022
        format = format.join('');
1023
1024
        map.push({
1025
          index: index,
1026
          key: data.key,
1027
          apply: data[func],
1028
          matcher: data.regex
1029
        });
1030
      }
1031
    });
1032
1033
    return {
1034
      regex: new RegExp('^' + regex.join('') + '$'),
1035
      map: orderByFilter(map, 'index')
1036
    };
1037
  }
1038
1039
  this.filter = function(date, format) {
1040
    if (!angular.isDate(date) || isNaN(date) || !format) {
1041
      return '';
1042
    }
1043
1044
    format = $locale.DATETIME_FORMATS[format] || format;
1045
1046
    if ($locale.id !== localeId) {
1047
      this.init();
1048
    }
1049
1050
    if (!this.formatters[format]) {
1051
      this.formatters[format] = createParser(format, 'formatter');
1052
    }
1053
1054
    var parser = this.formatters[format],
1055
      map = parser.map;
1056
1057
    var _format = format;
1058
1059
    return map.reduce(function(str, mapper, i) {
1060
      var match = _format.match(new RegExp('(.*)' + mapper.key));
1061
      if (match && angular.isString(match[1])) {
1062
        str += match[1];
1063
        _format = _format.replace(match[1] + mapper.key, '');
1064
      }
1065
1066
      var endStr = i === map.length - 1 ? _format : '';
1067
1068
      if (mapper.apply) {
1069
        return str + mapper.apply.call(null, date) + endStr;
1070
      }
1071
1072
      return str + endStr;
1073
    }, '');
1074
  };
1075
1076
  this.parse = function(input, format, baseDate) {
1077
    if (!angular.isString(input) || !format) {
1078
      return input;
1079
    }
1080
1081
    format = $locale.DATETIME_FORMATS[format] || format;
1082
    format = format.replace(SPECIAL_CHARACTERS_REGEXP, '\\$&');
1083
1084
    if ($locale.id !== localeId) {
1085
      this.init();
1086
    }
1087
1088
    if (!this.parsers[format]) {
1089
      this.parsers[format] = createParser(format, 'apply');
1090
    }
1091
1092
    var parser = this.parsers[format],
1093
        regex = parser.regex,
1094
        map = parser.map,
1095
        results = input.match(regex),
1096
        tzOffset = false;
1097
    if (results && results.length) {
1098
      var fields, dt;
1099
      if (angular.isDate(baseDate) && !isNaN(baseDate.getTime())) {
1100
        fields = {
1101
          year: baseDate.getFullYear(),
1102
          month: baseDate.getMonth(),
1103
          date: baseDate.getDate(),
1104
          hours: baseDate.getHours(),
1105
          minutes: baseDate.getMinutes(),
1106
          seconds: baseDate.getSeconds(),
1107
          milliseconds: baseDate.getMilliseconds()
1108
        };
1109
      } else {
1110
        if (baseDate) {
1111
          $log.warn('dateparser:', 'baseDate is not a valid date');
1112
        }
1113
        fields = { year: 1900, month: 0, date: 1, hours: 0, minutes: 0, seconds: 0, milliseconds: 0 };
1114
      }
1115
1116
      for (var i = 1, n = results.length; i < n; i++) {
1117
        var mapper = map[i - 1];
1118
        if (mapper.matcher === 'Z') {
1119
          tzOffset = true;
1120
        }
1121
1122
        if (mapper.apply) {
1123
          mapper.apply.call(fields, results[i]);
1124
        }
1125
      }
1126
1127
      var datesetter = tzOffset ? Date.prototype.setUTCFullYear :
1128
        Date.prototype.setFullYear;
1129
      var timesetter = tzOffset ? Date.prototype.setUTCHours :
1130
        Date.prototype.setHours;
1131
1132
      if (isValid(fields.year, fields.month, fields.date)) {
1133
        if (angular.isDate(baseDate) && !isNaN(baseDate.getTime()) && !tzOffset) {
1134
          dt = new Date(baseDate);
1135
          datesetter.call(dt, fields.year, fields.month, fields.date);
1136
          timesetter.call(dt, fields.hours, fields.minutes,
1137
            fields.seconds, fields.milliseconds);
1138
        } else {
1139
          dt = new Date(0);
1140
          datesetter.call(dt, fields.year, fields.month, fields.date);
1141
          timesetter.call(dt, fields.hours || 0, fields.minutes || 0,
1142
            fields.seconds || 0, fields.milliseconds || 0);
1143
        }
1144
      }
1145
1146
      return dt;
1147
    }
1148
  };
1149
1150
  // Check if date is valid for specific month (and year for February).
1151
  // Month: 0 = Jan, 1 = Feb, etc
1152
  function isValid(year, month, date) {
1153
    if (date < 1) {
1154
      return false;
1155
    }
1156
1157
    if (month === 1 && date > 28) {
1158
      return date === 29 && (year % 4 === 0 && year % 100 !== 0 || year % 400 === 0);
1159
    }
1160
1161
    if (month === 3 || month === 5 || month === 8 || month === 10) {
1162
      return date < 31;
1163
    }
1164
1165
    return true;
1166
  }
1167
1168
  function toInt(str) {
1169
    return parseInt(str, 10);
1170
  }
1171
1172
  this.toTimezone = toTimezone;
1173
  this.fromTimezone = fromTimezone;
1174
  this.timezoneToOffset = timezoneToOffset;
1175
  this.addDateMinutes = addDateMinutes;
1176
  this.convertTimezoneToLocal = convertTimezoneToLocal;
1177
1178
  function toTimezone(date, timezone) {
1179
    return date && timezone ? convertTimezoneToLocal(date, timezone) : date;
1180
  }
1181
1182
  function fromTimezone(date, timezone) {
1183
    return date && timezone ? convertTimezoneToLocal(date, timezone, true) : date;
1184
  }
1185
1186
  //https://github.com/angular/angular.js/blob/4daafd3dbe6a80d578f5a31df1bb99c77559543e/src/Angular.js#L1207
1187
  function timezoneToOffset(timezone, fallback) {
1188
    var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;
1189
    return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;
1190
  }
1191
1192
  function addDateMinutes(date, minutes) {
1193
    date = new Date(date.getTime());
1194
    date.setMinutes(date.getMinutes() + minutes);
1195
    return date;
1196
  }
1197
1198
  function convertTimezoneToLocal(date, timezone, reverse) {
1199
    reverse = reverse ? -1 : 1;
1200
    var timezoneOffset = timezoneToOffset(timezone, date.getTimezoneOffset());
1201
    return addDateMinutes(date, reverse * (timezoneOffset - date.getTimezoneOffset()));
1202
  }
1203
}]);
1204
1205
// Avoiding use of ng-class as it creates a lot of watchers when a class is to be applied to
1206
// at most one element.
1207
angular.module('ui.bootstrap.isClass', [])
1208
.directive('uibIsClass', [
1209
         '$animate',
1210 View Code Duplication
function ($animate) {
1211
  //                    11111111          22222222
1212
  var ON_REGEXP = /^\s*([\s\S]+?)\s+on\s+([\s\S]+?)\s*$/;
1213
  //                    11111111           22222222
1214
  var IS_REGEXP = /^\s*([\s\S]+?)\s+for\s+([\s\S]+?)\s*$/;
1215
1216
  var dataPerTracked = {};
0 ignored issues
show
Unused Code introduced by
The variable dataPerTracked seems to be never used. Consider removing it.
Loading history...
1217
1218
  return {
1219
    restrict: 'A',
1220
    compile: function(tElement, tAttrs) {
1221
      var linkedScopes = [];
1222
      var instances = [];
1223
      var expToData = {};
1224
      var lastActivated = null;
1225
      var onExpMatches = tAttrs.uibIsClass.match(ON_REGEXP);
1226
      var onExp = onExpMatches[2];
1227
      var expsStr = onExpMatches[1];
1228
      var exps = expsStr.split(',');
1229
1230
      return linkFn;
1231
1232
      function linkFn(scope, element, attrs) {
1233
        linkedScopes.push(scope);
1234
        instances.push({
1235
          scope: scope,
1236
          element: element
1237
        });
1238
1239
        exps.forEach(function(exp, k) {
1240
          addForExp(exp, scope);
1241
        });
1242
1243
        scope.$on('$destroy', removeScope);
1244
      }
1245
1246
      function addForExp(exp, scope) {
1247
        var matches = exp.match(IS_REGEXP);
1248
        var clazz = scope.$eval(matches[1]);
1249
        var compareWithExp = matches[2];
1250
        var data = expToData[exp];
1251
        if (!data) {
1252
          var watchFn = function(compareWithVal) {
1253
            var newActivated = null;
1254
            instances.some(function(instance) {
1255
              var thisVal = instance.scope.$eval(onExp);
1256
              if (thisVal === compareWithVal) {
1257
                newActivated = instance;
1258
                return true;
1259
              }
1260
            });
1261
            if (data.lastActivated !== newActivated) {
1262
              if (data.lastActivated) {
1263
                $animate.removeClass(data.lastActivated.element, clazz);
1264
              }
1265
              if (newActivated) {
1266
                $animate.addClass(newActivated.element, clazz);
1267
              }
1268
              data.lastActivated = newActivated;
1269
            }
1270
          };
1271
          expToData[exp] = data = {
1272
            lastActivated: null,
1273
            scope: scope,
1274
            watchFn: watchFn,
1275
            compareWithExp: compareWithExp,
1276
            watcher: scope.$watch(compareWithExp, watchFn)
1277
          };
1278
        }
1279
        data.watchFn(scope.$eval(compareWithExp));
1280
      }
1281
1282
      function removeScope(e) {
1283
        var removedScope = e.targetScope;
1284
        var index = linkedScopes.indexOf(removedScope);
1285
        linkedScopes.splice(index, 1);
1286
        instances.splice(index, 1);
1287
        if (linkedScopes.length) {
1288
          var newWatchScope = linkedScopes[0];
1289
          angular.forEach(expToData, function(data) {
1290
            if (data.scope === removedScope) {
1291
              data.watcher = newWatchScope.$watch(data.compareWithExp, data.watchFn);
1292
              data.scope = newWatchScope;
1293
            }
1294
          });
1295
        } else {
1296
          expToData = {};
1297
        }
1298
      }
1299
    }
1300
  };
1301
}]);
1302
angular.module('ui.bootstrap.datepicker', ['ui.bootstrap.dateparser', 'ui.bootstrap.isClass'])
1303
1304
.value('$datepickerSuppressError', false)
1305
1306
.value('$datepickerLiteralWarning', true)
1307
1308
.constant('uibDatepickerConfig', {
1309
  datepickerMode: 'day',
1310
  formatDay: 'dd',
1311
  formatMonth: 'MMMM',
1312
  formatYear: 'yyyy',
1313
  formatDayHeader: 'EEE',
1314
  formatDayTitle: 'MMMM yyyy',
1315
  formatMonthTitle: 'yyyy',
1316
  maxDate: null,
1317
  maxMode: 'year',
1318
  minDate: null,
1319
  minMode: 'day',
1320
  ngModelOptions: {},
1321
  shortcutPropagation: false,
1322
  showWeeks: true,
1323
  yearColumns: 5,
1324
  yearRows: 4
1325
})
1326
1327
.controller('UibDatepickerController', ['$scope', '$attrs', '$parse', '$interpolate', '$locale', '$log', 'dateFilter', 'uibDatepickerConfig', '$datepickerLiteralWarning', '$datepickerSuppressError', 'uibDateParser',
1328 View Code Duplication
  function($scope, $attrs, $parse, $interpolate, $locale, $log, dateFilter, datepickerConfig, $datepickerLiteralWarning, $datepickerSuppressError, dateParser) {
1329
  var self = this,
1330
      ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl;
1331
      ngModelOptions = {},
1332
      watchListeners = [],
1333
      optionsUsed = !!$attrs.datepickerOptions;
0 ignored issues
show
Unused Code introduced by
The variable optionsUsed seems to be never used. Consider removing it.
Loading history...
1334
1335
  if (!$scope.datepickerOptions) {
1336
    $scope.datepickerOptions = {};
1337
  }
1338
1339
  // Modes chain
1340
  this.modes = ['day', 'month', 'year'];
1341
1342
  [
1343
    'customClass',
1344
    'dateDisabled',
1345
    'datepickerMode',
1346
    'formatDay',
1347
    'formatDayHeader',
1348
    'formatDayTitle',
1349
    'formatMonth',
1350
    'formatMonthTitle',
1351
    'formatYear',
1352
    'maxDate',
1353
    'maxMode',
1354
    'minDate',
1355
    'minMode',
1356
    'showWeeks',
1357
    'shortcutPropagation',
1358
    'startingDay',
1359
    'yearColumns',
1360
    'yearRows'
1361
  ].forEach(function(key) {
1362
    switch (key) {
1363
      case 'customClass':
1364
      case 'dateDisabled':
1365
        $scope[key] = $scope.datepickerOptions[key] || angular.noop;
1366
        break;
1367
      case 'datepickerMode':
1368
        $scope.datepickerMode = angular.isDefined($scope.datepickerOptions.datepickerMode) ?
1369
          $scope.datepickerOptions.datepickerMode : datepickerConfig.datepickerMode;
1370
        break;
1371
      case 'formatDay':
1372
      case 'formatDayHeader':
1373
      case 'formatDayTitle':
1374
      case 'formatMonth':
1375
      case 'formatMonthTitle':
1376
      case 'formatYear':
1377
        self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1378
          $interpolate($scope.datepickerOptions[key])($scope.$parent) :
1379
          datepickerConfig[key];
1380
        break;
1381
      case 'showWeeks':
1382
      case 'shortcutPropagation':
1383
      case 'yearColumns':
1384
      case 'yearRows':
1385
        self[key] = angular.isDefined($scope.datepickerOptions[key]) ?
1386
          $scope.datepickerOptions[key] : datepickerConfig[key];
1387
        break;
1388
      case 'startingDay':
1389
        if (angular.isDefined($scope.datepickerOptions.startingDay)) {
1390
          self.startingDay = $scope.datepickerOptions.startingDay;
1391
        } else if (angular.isNumber(datepickerConfig.startingDay)) {
1392
          self.startingDay = datepickerConfig.startingDay;
1393
        } else {
1394
          self.startingDay = ($locale.DATETIME_FORMATS.FIRSTDAYOFWEEK + 8) % 7;
1395
        }
1396
1397
        break;
1398
      case 'maxDate':
1399
      case 'minDate':
1400
        $scope.$watch('datepickerOptions.' + key, function(value) {
1401
          if (value) {
1402
            if (angular.isDate(value)) {
1403
              self[key] = dateParser.fromTimezone(new Date(value), ngModelOptions.timezone);
1404
            } else {
1405
              if ($datepickerLiteralWarning) {
1406
                $log.warn('Literal date support has been deprecated, please switch to date object usage');
1407
              }
1408
1409
              self[key] = new Date(dateFilter(value, 'medium'));
1410
            }
1411
          } else {
1412
            self[key] = datepickerConfig[key] ?
1413
              dateParser.fromTimezone(new Date(datepickerConfig[key]), ngModelOptions.timezone) :
1414
              null;
1415
          }
1416
1417
          self.refreshView();
1418
        });
1419
1420
        break;
1421
      case 'maxMode':
1422
      case 'minMode':
1423
        if ($scope.datepickerOptions[key]) {
1424
          $scope.$watch(function() { return $scope.datepickerOptions[key]; }, function(value) {
1425
            self[key] = $scope[key] = angular.isDefined(value) ? value : datepickerOptions[key];
1426
            if (key === 'minMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) < self.modes.indexOf(self[key]) ||
1427
              key === 'maxMode' && self.modes.indexOf($scope.datepickerOptions.datepickerMode) > self.modes.indexOf(self[key])) {
1428
              $scope.datepickerMode = self[key];
1429
              $scope.datepickerOptions.datepickerMode = self[key];
1430
            }
1431
          });
1432
        } else {
1433
          self[key] = $scope[key] = datepickerConfig[key] || null;
1434
        }
1435
1436
        break;
1437
    }
1438
  });
1439
1440
  $scope.uniqueId = 'datepicker-' + $scope.$id + '-' + Math.floor(Math.random() * 10000);
1441
1442
  $scope.disabled = angular.isDefined($attrs.disabled) || false;
1443
  if (angular.isDefined($attrs.ngDisabled)) {
1444
    watchListeners.push($scope.$parent.$watch($attrs.ngDisabled, function(disabled) {
1445
      $scope.disabled = disabled;
1446
      self.refreshView();
1447
    }));
1448
  }
1449
1450
  $scope.isActive = function(dateObject) {
1451
    if (self.compare(dateObject.date, self.activeDate) === 0) {
1452
      $scope.activeDateId = dateObject.uid;
1453
      return true;
1454
    }
1455
    return false;
1456
  };
1457
1458
  this.init = function(ngModelCtrl_) {
1459
    ngModelCtrl = ngModelCtrl_;
1460
    ngModelOptions = ngModelCtrl_.$options || datepickerConfig.ngModelOptions;
1461
    if ($scope.datepickerOptions.initDate) {
1462
      self.activeDate = dateParser.fromTimezone($scope.datepickerOptions.initDate, ngModelOptions.timezone) || new Date();
1463
      $scope.$watch('datepickerOptions.initDate', function(initDate) {
1464
        if (initDate && (ngModelCtrl.$isEmpty(ngModelCtrl.$modelValue) || ngModelCtrl.$invalid)) {
1465
          self.activeDate = dateParser.fromTimezone(initDate, ngModelOptions.timezone);
1466
          self.refreshView();
1467
        }
1468
      });
1469
    } else {
1470
      self.activeDate = new Date();
1471
    }
1472
1473
    this.activeDate = ngModelCtrl.$modelValue ?
1474
      dateParser.fromTimezone(new Date(ngModelCtrl.$modelValue), ngModelOptions.timezone) :
1475
      dateParser.fromTimezone(new Date(), ngModelOptions.timezone);
1476
1477
    ngModelCtrl.$render = function() {
1478
      self.render();
1479
    };
1480
  };
1481
1482
  this.render = function() {
1483
    if (ngModelCtrl.$viewValue) {
1484
      var date = new Date(ngModelCtrl.$viewValue),
1485
          isValid = !isNaN(date);
1486
1487
      if (isValid) {
1488
        this.activeDate = dateParser.fromTimezone(date, ngModelOptions.timezone);
1489
      } else if (!$datepickerSuppressError) {
1490
        $log.error('Datepicker directive: "ng-model" value must be a Date object');
1491
      }
1492
    }
1493
    this.refreshView();
1494
  };
1495
1496
  this.refreshView = function() {
1497
    if (this.element) {
1498
      $scope.selectedDt = null;
1499
      this._refreshView();
1500
      if ($scope.activeDt) {
1501
        $scope.activeDateId = $scope.activeDt.uid;
1502
      }
1503
1504
      var date = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1505
      date = dateParser.fromTimezone(date, ngModelOptions.timezone);
1506
      ngModelCtrl.$setValidity('dateDisabled', !date ||
1507
        this.element && !this.isDisabled(date));
1508
    }
1509
  };
1510
1511
  this.createDateObject = function(date, format) {
1512
    var model = ngModelCtrl.$viewValue ? new Date(ngModelCtrl.$viewValue) : null;
1513
    model = dateParser.fromTimezone(model, ngModelOptions.timezone);
1514
    var today = new Date();
1515
    today = dateParser.fromTimezone(today, ngModelOptions.timezone);
1516
    var time = this.compare(date, today);
1517
    var dt = {
1518
      date: date,
1519
      label: dateParser.filter(date, format),
1520
      selected: model && this.compare(date, model) === 0,
1521
      disabled: this.isDisabled(date),
1522
      past: time < 0,
1523
      current: time === 0,
1524
      future: time > 0,
1525
      customClass: this.customClass(date) || null
1526
    };
1527
1528
    if (model && this.compare(date, model) === 0) {
1529
      $scope.selectedDt = dt;
1530
    }
1531
1532
    if (self.activeDate && this.compare(dt.date, self.activeDate) === 0) {
1533
      $scope.activeDt = dt;
1534
    }
1535
1536
    return dt;
1537
  };
1538
1539
  this.isDisabled = function(date) {
1540
    return $scope.disabled ||
1541
      this.minDate && this.compare(date, this.minDate) < 0 ||
1542
      this.maxDate && this.compare(date, this.maxDate) > 0 ||
1543
      $scope.dateDisabled && $scope.dateDisabled({date: date, mode: $scope.datepickerMode});
1544
  };
1545
1546
  this.customClass = function(date) {
1547
    return $scope.customClass({date: date, mode: $scope.datepickerMode});
1548
  };
1549
1550
  // Split array into smaller arrays
1551
  this.split = function(arr, size) {
1552
    var arrays = [];
1553
    while (arr.length > 0) {
1554
      arrays.push(arr.splice(0, size));
1555
    }
1556
    return arrays;
1557
  };
1558
1559
  $scope.select = function(date) {
1560
    if ($scope.datepickerMode === self.minMode) {
1561
      var dt = ngModelCtrl.$viewValue ? dateParser.fromTimezone(new Date(ngModelCtrl.$viewValue), ngModelOptions.timezone) : new Date(0, 0, 0, 0, 0, 0, 0);
1562
      dt.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
1563
      dt = dateParser.toTimezone(dt, ngModelOptions.timezone);
1564
      ngModelCtrl.$setViewValue(dt);
1565
      ngModelCtrl.$render();
1566
    } else {
1567
      self.activeDate = date;
1568
      setMode(self.modes[self.modes.indexOf($scope.datepickerMode) - 1]);
1569
1570
      $scope.$emit('uib:datepicker.mode');
1571
    }
1572
1573
    $scope.$broadcast('uib:datepicker.focus');
1574
  };
1575
1576
  $scope.move = function(direction) {
1577
    var year = self.activeDate.getFullYear() + direction * (self.step.years || 0),
1578
        month = self.activeDate.getMonth() + direction * (self.step.months || 0);
1579
    self.activeDate.setFullYear(year, month, 1);
1580
    self.refreshView();
1581
  };
1582
1583
  $scope.toggleMode = function(direction) {
1584
    direction = direction || 1;
1585
1586
    if ($scope.datepickerMode === self.maxMode && direction === 1 ||
1587
      $scope.datepickerMode === self.minMode && direction === -1) {
1588
      return;
1589
    }
1590
1591
    setMode(self.modes[self.modes.indexOf($scope.datepickerMode) + direction]);
1592
1593
    $scope.$emit('uib:datepicker.mode');
1594
  };
1595
1596
  // Key event mapper
1597
  $scope.keys = { 13: 'enter', 32: 'space', 33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home', 37: 'left', 38: 'up', 39: 'right', 40: 'down' };
1598
1599
  var focusElement = function() {
1600
    self.element[0].focus();
1601
  };
1602
1603
  // Listen for focus requests from popup directive
1604
  $scope.$on('uib:datepicker.focus', focusElement);
1605
1606
  $scope.keydown = function(evt) {
1607
    var key = $scope.keys[evt.which];
1608
1609
    if (!key || evt.shiftKey || evt.altKey || $scope.disabled) {
1610
      return;
1611
    }
1612
1613
    evt.preventDefault();
1614
    if (!self.shortcutPropagation) {
1615
      evt.stopPropagation();
1616
    }
1617
1618
    if (key === 'enter' || key === 'space') {
1619
      if (self.isDisabled(self.activeDate)) {
1620
        return; // do nothing
1621
      }
1622
      $scope.select(self.activeDate);
1623
    } else if (evt.ctrlKey && (key === 'up' || key === 'down')) {
1624
      $scope.toggleMode(key === 'up' ? 1 : -1);
1625
    } else {
1626
      self.handleKeyDown(key, evt);
1627
      self.refreshView();
1628
    }
1629
  };
1630
1631
  $scope.$on('$destroy', function() {
1632
    //Clear all watch listeners on destroy
1633
    while (watchListeners.length) {
1634
      watchListeners.shift()();
1635
    }
1636
  });
1637
1638
  function setMode(mode) {
1639
    $scope.datepickerMode = mode;
1640
    $scope.datepickerOptions.datepickerMode = mode;
1641
  }
1642
}])
1643
1644 View Code Duplication
.controller('UibDaypickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1645
  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
1646
1647
  this.step = { months: 1 };
1648
  this.element = $element;
1649
  function getDaysInMonth(year, month) {
1650
    return month === 1 && year % 4 === 0 &&
1651
      (year % 100 !== 0 || year % 400 === 0) ? 29 : DAYS_IN_MONTH[month];
1652
  }
1653
1654
  this.init = function(ctrl) {
1655
    angular.extend(ctrl, this);
1656
    scope.showWeeks = ctrl.showWeeks;
1657
    ctrl.refreshView();
1658
  };
1659
1660
  this.getDates = function(startDate, n) {
1661
    var dates = new Array(n), current = new Date(startDate), i = 0, date;
1662
    while (i < n) {
1663
      date = new Date(current);
1664
      dates[i++] = date;
1665
      current.setDate(current.getDate() + 1);
1666
    }
1667
    return dates;
1668
  };
1669
1670
  this._refreshView = function() {
1671
    var year = this.activeDate.getFullYear(),
1672
      month = this.activeDate.getMonth(),
1673
      firstDayOfMonth = new Date(this.activeDate);
1674
1675
    firstDayOfMonth.setFullYear(year, month, 1);
1676
1677
    var difference = this.startingDay - firstDayOfMonth.getDay(),
1678
      numDisplayedFromPreviousMonth = difference > 0 ?
1679
        7 - difference : - difference,
1680
      firstDate = new Date(firstDayOfMonth);
1681
1682
    if (numDisplayedFromPreviousMonth > 0) {
1683
      firstDate.setDate(-numDisplayedFromPreviousMonth + 1);
1684
    }
1685
1686
    // 42 is the number of days on a six-week calendar
1687
    var days = this.getDates(firstDate, 42);
1688
    for (var i = 0; i < 42; i ++) {
1689
      days[i] = angular.extend(this.createDateObject(days[i], this.formatDay), {
1690
        secondary: days[i].getMonth() !== month,
1691
        uid: scope.uniqueId + '-' + i
1692
      });
1693
    }
1694
1695
    scope.labels = new Array(7);
1696
    for (var j = 0; j < 7; j++) {
1697
      scope.labels[j] = {
1698
        abbr: dateFilter(days[j].date, this.formatDayHeader),
1699
        full: dateFilter(days[j].date, 'EEEE')
1700
      };
1701
    }
1702
1703
    scope.title = dateFilter(this.activeDate, this.formatDayTitle);
1704
    scope.rows = this.split(days, 7);
1705
1706
    if (scope.showWeeks) {
1707
      scope.weekNumbers = [];
1708
      var thursdayIndex = (4 + 7 - this.startingDay) % 7,
1709
          numWeeks = scope.rows.length;
1710
      for (var curWeek = 0; curWeek < numWeeks; curWeek++) {
1711
        scope.weekNumbers.push(
1712
          getISO8601WeekNumber(scope.rows[curWeek][thursdayIndex].date));
1713
      }
1714
    }
1715
  };
1716
1717
  this.compare = function(date1, date2) {
1718
    var _date1 = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate());
1719
    var _date2 = new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
1720
    _date1.setFullYear(date1.getFullYear());
1721
    _date2.setFullYear(date2.getFullYear());
1722
    return _date1 - _date2;
1723
  };
1724
1725
  function getISO8601WeekNumber(date) {
1726
    var checkDate = new Date(date);
1727
    checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7)); // Thursday
1728
    var time = checkDate.getTime();
1729
    checkDate.setMonth(0); // Compare with Jan 1
1730
    checkDate.setDate(1);
1731
    return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
1732
  }
1733
1734
  this.handleKeyDown = function(key, evt) {
1735
    var date = this.activeDate.getDate();
1736
1737
    if (key === 'left') {
1738
      date = date - 1;
1739
    } else if (key === 'up') {
1740
      date = date - 7;
1741
    } else if (key === 'right') {
1742
      date = date + 1;
1743
    } else if (key === 'down') {
1744
      date = date + 7;
1745
    } else if (key === 'pageup' || key === 'pagedown') {
1746
      var month = this.activeDate.getMonth() + (key === 'pageup' ? - 1 : 1);
1747
      this.activeDate.setMonth(month, 1);
1748
      date = Math.min(getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth()), date);
1749
    } else if (key === 'home') {
1750
      date = 1;
1751
    } else if (key === 'end') {
1752
      date = getDaysInMonth(this.activeDate.getFullYear(), this.activeDate.getMonth());
1753
    }
1754
    this.activeDate.setDate(date);
1755
  };
1756
}])
1757
1758 View Code Duplication
.controller('UibMonthpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1759
  this.step = { years: 1 };
1760
  this.element = $element;
1761
1762
  this.init = function(ctrl) {
1763
    angular.extend(ctrl, this);
1764
    ctrl.refreshView();
1765
  };
1766
1767
  this._refreshView = function() {
1768
    var months = new Array(12),
1769
        year = this.activeDate.getFullYear(),
1770
        date;
1771
1772
    for (var i = 0; i < 12; i++) {
1773
      date = new Date(this.activeDate);
1774
      date.setFullYear(year, i, 1);
1775
      months[i] = angular.extend(this.createDateObject(date, this.formatMonth), {
1776
        uid: scope.uniqueId + '-' + i
1777
      });
1778
    }
1779
1780
    scope.title = dateFilter(this.activeDate, this.formatMonthTitle);
1781
    scope.rows = this.split(months, 3);
1782
  };
1783
1784
  this.compare = function(date1, date2) {
1785
    var _date1 = new Date(date1.getFullYear(), date1.getMonth());
1786
    var _date2 = new Date(date2.getFullYear(), date2.getMonth());
1787
    _date1.setFullYear(date1.getFullYear());
1788
    _date2.setFullYear(date2.getFullYear());
1789
    return _date1 - _date2;
1790
  };
1791
1792
  this.handleKeyDown = function(key, evt) {
1793
    var date = this.activeDate.getMonth();
1794
1795
    if (key === 'left') {
1796
      date = date - 1;
1797
    } else if (key === 'up') {
1798
      date = date - 3;
1799
    } else if (key === 'right') {
1800
      date = date + 1;
1801
    } else if (key === 'down') {
1802
      date = date + 3;
1803
    } else if (key === 'pageup' || key === 'pagedown') {
1804
      var year = this.activeDate.getFullYear() + (key === 'pageup' ? - 1 : 1);
1805
      this.activeDate.setFullYear(year);
1806
    } else if (key === 'home') {
1807
      date = 0;
1808
    } else if (key === 'end') {
1809
      date = 11;
1810
    }
1811
    this.activeDate.setMonth(date);
1812
  };
1813
}])
1814
1815 View Code Duplication
.controller('UibYearpickerController', ['$scope', '$element', 'dateFilter', function(scope, $element, dateFilter) {
1816
  var columns, range;
1817
  this.element = $element;
1818
1819
  function getStartingYear(year) {
1820
    return parseInt((year - 1) / range, 10) * range + 1;
1821
  }
1822
1823
  this.yearpickerInit = function() {
1824
    columns = this.yearColumns;
1825
    range = this.yearRows * columns;
1826
    this.step = { years: range };
1827
  };
1828
1829
  this._refreshView = function() {
1830
    var years = new Array(range), date;
0 ignored issues
show
Coding Style Best Practice introduced by
Using the Array constructor is generally discouraged. Consider using an array literal instead.
Loading history...
1831
1832
    for (var i = 0, start = getStartingYear(this.activeDate.getFullYear()); i < range; i++) {
1833
      date = new Date(this.activeDate);
1834
      date.setFullYear(start + i, 0, 1);
1835
      years[i] = angular.extend(this.createDateObject(date, this.formatYear), {
1836
        uid: scope.uniqueId + '-' + i
1837
      });
1838
    }
1839
1840
    scope.title = [years[0].label, years[range - 1].label].join(' - ');
1841
    scope.rows = this.split(years, columns);
1842
    scope.columns = columns;
1843
  };
1844
1845
  this.compare = function(date1, date2) {
1846
    return date1.getFullYear() - date2.getFullYear();
1847
  };
1848
1849
  this.handleKeyDown = function(key, evt) {
1850
    var date = this.activeDate.getFullYear();
1851
1852
    if (key === 'left') {
1853
      date = date - 1;
1854
    } else if (key === 'up') {
1855
      date = date - columns;
1856
    } else if (key === 'right') {
1857
      date = date + 1;
1858
    } else if (key === 'down') {
1859
      date = date + columns;
1860
    } else if (key === 'pageup' || key === 'pagedown') {
1861
      date += (key === 'pageup' ? - 1 : 1) * range;
1862
    } else if (key === 'home') {
1863
      date = getStartingYear(this.activeDate.getFullYear());
1864
    } else if (key === 'end') {
1865
      date = getStartingYear(this.activeDate.getFullYear()) + range - 1;
1866
    }
1867
    this.activeDate.setFullYear(date);
1868
  };
1869
}])
1870
1871 View Code Duplication
.directive('uibDatepicker', function() {
1872
  return {
1873
    replace: true,
1874
    templateUrl: function(element, attrs) {
1875
      return attrs.templateUrl || 'uib/template/datepicker/datepicker.html';
1876
    },
1877
    scope: {
1878
      datepickerOptions: '=?'
1879
    },
1880
    require: ['uibDatepicker', '^ngModel'],
1881
    controller: 'UibDatepickerController',
1882
    controllerAs: 'datepicker',
1883
    link: function(scope, element, attrs, ctrls) {
1884
      var datepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
1885
1886
      datepickerCtrl.init(ngModelCtrl);
1887
    }
1888
  };
1889
})
1890
1891
.directive('uibDaypicker', function() {
1892
  return {
1893
    replace: true,
1894
    templateUrl: function(element, attrs) {
1895
      return attrs.templateUrl || 'uib/template/datepicker/day.html';
1896
    },
1897
    require: ['^uibDatepicker', 'uibDaypicker'],
1898
    controller: 'UibDaypickerController',
1899
    link: function(scope, element, attrs, ctrls) {
1900
      var datepickerCtrl = ctrls[0],
1901
        daypickerCtrl = ctrls[1];
1902
1903
      daypickerCtrl.init(datepickerCtrl);
1904
    }
1905
  };
1906
})
1907
1908
.directive('uibMonthpicker', function() {
1909
  return {
1910
    replace: true,
1911
    templateUrl: function(element, attrs) {
1912
      return attrs.templateUrl || 'uib/template/datepicker/month.html';
1913
    },
1914
    require: ['^uibDatepicker', 'uibMonthpicker'],
1915
    controller: 'UibMonthpickerController',
1916
    link: function(scope, element, attrs, ctrls) {
1917
      var datepickerCtrl = ctrls[0],
1918
        monthpickerCtrl = ctrls[1];
1919
1920
      monthpickerCtrl.init(datepickerCtrl);
1921
    }
1922
  };
1923
})
1924
1925
.directive('uibYearpicker', function() {
1926
  return {
1927
    replace: true,
1928
    templateUrl: function(element, attrs) {
1929
      return attrs.templateUrl || 'uib/template/datepicker/year.html';
1930
    },
1931
    require: ['^uibDatepicker', 'uibYearpicker'],
1932
    controller: 'UibYearpickerController',
1933
    link: function(scope, element, attrs, ctrls) {
1934
      var ctrl = ctrls[0];
1935
      angular.extend(ctrl, ctrls[1]);
1936
      ctrl.yearpickerInit();
1937
1938
      ctrl.refreshView();
1939
    }
1940
  };
1941
});
1942
1943
angular.module('ui.bootstrap.position', [])
1944
1945
/**
1946
 * A set of utility methods for working with the DOM.
1947
 * It is meant to be used where we need to absolute-position elements in
1948
 * relation to another element (this is the case for tooltips, popovers,
1949
 * typeahead suggestions etc.).
1950
 */
1951 View Code Duplication
  .factory('$uibPosition', ['$document', '$window', function($document, $window) {
1952
    /**
1953
     * Used by scrollbarWidth() function to cache scrollbar's width.
1954
     * Do not access this variable directly, use scrollbarWidth() instead.
1955
     */
1956
    var SCROLLBAR_WIDTH;
1957
    /**
1958
     * scrollbar on body and html element in IE and Edge overlay
1959
     * content and should be considered 0 width.
1960
     */
1961
    var BODY_SCROLLBAR_WIDTH;
1962
    var OVERFLOW_REGEX = {
1963
      normal: /(auto|scroll)/,
1964
      hidden: /(auto|scroll|hidden)/
1965
    };
1966
    var PLACEMENT_REGEX = {
1967
      auto: /\s?auto?\s?/i,
1968
      primary: /^(top|bottom|left|right)$/,
1969
      secondary: /^(top|bottom|left|right|center)$/,
1970
      vertical: /^(top|bottom)$/
1971
    };
1972
    var BODY_REGEX = /(HTML|BODY)/;
1973
1974
    return {
1975
1976
      /**
1977
       * Provides a raw DOM element from a jQuery/jQLite element.
1978
       *
1979
       * @param {element} elem - The element to convert.
1980
       *
1981
       * @returns {element} A HTML element.
1982
       */
1983
      getRawNode: function(elem) {
1984
        return elem.nodeName ? elem : elem[0] || elem;
1985
      },
1986
1987
      /**
1988
       * Provides a parsed number for a style property.  Strips
1989
       * units and casts invalid numbers to 0.
1990
       *
1991
       * @param {string} value - The style value to parse.
1992
       *
1993
       * @returns {number} A valid number.
1994
       */
1995
      parseStyle: function(value) {
1996
        value = parseFloat(value);
1997
        return isFinite(value) ? value : 0;
1998
      },
1999
2000
      /**
2001
       * Provides the closest positioned ancestor.
2002
       *
2003
       * @param {element} element - The element to get the offest parent for.
2004
       *
2005
       * @returns {element} The closest positioned ancestor.
2006
       */
2007
      offsetParent: function(elem) {
2008
        elem = this.getRawNode(elem);
2009
2010
        var offsetParent = elem.offsetParent || $document[0].documentElement;
2011
2012
        function isStaticPositioned(el) {
2013
          return ($window.getComputedStyle(el).position || 'static') === 'static';
2014
        }
2015
2016
        while (offsetParent && offsetParent !== $document[0].documentElement && isStaticPositioned(offsetParent)) {
2017
          offsetParent = offsetParent.offsetParent;
2018
        }
2019
2020
        return offsetParent || $document[0].documentElement;
2021
      },
2022
2023
      /**
2024
       * Provides the scrollbar width, concept from TWBS measureScrollbar()
2025
       * function in https://github.com/twbs/bootstrap/blob/master/js/modal.js
2026
       * In IE and Edge, scollbar on body and html element overlay and should
2027
       * return a width of 0.
2028
       *
2029
       * @returns {number} The width of the browser scollbar.
2030
       */
2031
      scrollbarWidth: function(isBody) {
2032
        if (isBody) {
2033
          if (angular.isUndefined(BODY_SCROLLBAR_WIDTH)) {
2034
            var bodyElem = $document.find('body');
2035
            bodyElem.addClass('uib-position-body-scrollbar-measure');
2036
            BODY_SCROLLBAR_WIDTH = $window.innerWidth - bodyElem[0].clientWidth;
2037
            BODY_SCROLLBAR_WIDTH = isFinite(BODY_SCROLLBAR_WIDTH) ? BODY_SCROLLBAR_WIDTH : 0;
2038
            bodyElem.removeClass('uib-position-body-scrollbar-measure');
2039
          }
2040
          return BODY_SCROLLBAR_WIDTH;
2041
        }
2042
2043
        if (angular.isUndefined(SCROLLBAR_WIDTH)) {
2044
          var scrollElem = angular.element('<div class="uib-position-scrollbar-measure"></div>');
2045
          $document.find('body').append(scrollElem);
2046
          SCROLLBAR_WIDTH = scrollElem[0].offsetWidth - scrollElem[0].clientWidth;
2047
          SCROLLBAR_WIDTH = isFinite(SCROLLBAR_WIDTH) ? SCROLLBAR_WIDTH : 0;
2048
          scrollElem.remove();
2049
        }
2050
2051
        return SCROLLBAR_WIDTH;
2052
      },
2053
2054
      /**
2055
       * Provides the padding required on an element to replace the scrollbar.
2056
       *
2057
       * @returns {object} An object with the following properties:
2058
       *   <ul>
2059
       *     <li>**scrollbarWidth**: the width of the scrollbar</li>
2060
       *     <li>**widthOverflow**: whether the the width is overflowing</li>
2061
       *     <li>**right**: the amount of right padding on the element needed to replace the scrollbar</li>
2062
       *     <li>**rightOriginal**: the amount of right padding currently on the element</li>
2063
       *     <li>**heightOverflow**: whether the the height is overflowing</li>
2064
       *     <li>**bottom**: the amount of bottom padding on the element needed to replace the scrollbar</li>
2065
       *     <li>**bottomOriginal**: the amount of bottom padding currently on the element</li>
2066
       *   </ul>
2067
       */
2068
      scrollbarPadding: function(elem) {
2069
        elem = this.getRawNode(elem);
2070
2071
        var elemStyle = $window.getComputedStyle(elem);
2072
        var paddingRight = this.parseStyle(elemStyle.paddingRight);
2073
        var paddingBottom = this.parseStyle(elemStyle.paddingBottom);
2074
        var scrollParent = this.scrollParent(elem, false, true);
2075
        var scrollbarWidth = this.scrollbarWidth(scrollParent, BODY_REGEX.test(scrollParent.tagName));
2076
2077
        return {
2078
          scrollbarWidth: scrollbarWidth,
2079
          widthOverflow: scrollParent.scrollWidth > scrollParent.clientWidth,
2080
          right: paddingRight + scrollbarWidth,
2081
          originalRight: paddingRight,
2082
          heightOverflow: scrollParent.scrollHeight > scrollParent.clientHeight,
2083
          bottom: paddingBottom + scrollbarWidth,
2084
          originalBottom: paddingBottom
2085
         };
2086
      },
2087
2088
      /**
2089
       * Checks to see if the element is scrollable.
2090
       *
2091
       * @param {element} elem - The element to check.
2092
       * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2093
       *   default is false.
2094
       *
2095
       * @returns {boolean} Whether the element is scrollable.
2096
       */
2097
      isScrollable: function(elem, includeHidden) {
2098
        elem = this.getRawNode(elem);
2099
2100
        var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2101
        var elemStyle = $window.getComputedStyle(elem);
2102
        return overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX);
2103
      },
2104
2105
      /**
2106
       * Provides the closest scrollable ancestor.
2107
       * A port of the jQuery UI scrollParent method:
2108
       * https://github.com/jquery/jquery-ui/blob/master/ui/scroll-parent.js
2109
       *
2110
       * @param {element} elem - The element to find the scroll parent of.
2111
       * @param {boolean=} [includeHidden=false] - Should scroll style of 'hidden' be considered,
2112
       *   default is false.
2113
       * @param {boolean=} [includeSelf=false] - Should the element being passed be
2114
       * included in the scrollable llokup.
2115
       *
2116
       * @returns {element} A HTML element.
2117
       */
2118
      scrollParent: function(elem, includeHidden, includeSelf) {
2119
        elem = this.getRawNode(elem);
2120
2121
        var overflowRegex = includeHidden ? OVERFLOW_REGEX.hidden : OVERFLOW_REGEX.normal;
2122
        var documentEl = $document[0].documentElement;
2123
        var elemStyle = $window.getComputedStyle(elem);
2124
        if (includeSelf && overflowRegex.test(elemStyle.overflow + elemStyle.overflowY + elemStyle.overflowX)) {
2125
          return elem;
2126
        }
2127
        var excludeStatic = elemStyle.position === 'absolute';
2128
        var scrollParent = elem.parentElement || documentEl;
2129
2130
        if (scrollParent === documentEl || elemStyle.position === 'fixed') {
2131
          return documentEl;
2132
        }
2133
2134
        while (scrollParent.parentElement && scrollParent !== documentEl) {
2135
          var spStyle = $window.getComputedStyle(scrollParent);
2136
          if (excludeStatic && spStyle.position !== 'static') {
2137
            excludeStatic = false;
2138
          }
2139
2140
          if (!excludeStatic && overflowRegex.test(spStyle.overflow + spStyle.overflowY + spStyle.overflowX)) {
2141
            break;
2142
          }
2143
          scrollParent = scrollParent.parentElement;
2144
        }
2145
2146
        return scrollParent;
2147
      },
2148
2149
      /**
2150
       * Provides read-only equivalent of jQuery's position function:
2151
       * http://api.jquery.com/position/ - distance to closest positioned
2152
       * ancestor.  Does not account for margins by default like jQuery position.
2153
       *
2154
       * @param {element} elem - The element to caclulate the position on.
2155
       * @param {boolean=} [includeMargins=false] - Should margins be accounted
2156
       * for, default is false.
2157
       *
2158
       * @returns {object} An object with the following properties:
2159
       *   <ul>
2160
       *     <li>**width**: the width of the element</li>
2161
       *     <li>**height**: the height of the element</li>
2162
       *     <li>**top**: distance to top edge of offset parent</li>
2163
       *     <li>**left**: distance to left edge of offset parent</li>
2164
       *   </ul>
2165
       */
2166
      position: function(elem, includeMagins) {
2167
        elem = this.getRawNode(elem);
2168
2169
        var elemOffset = this.offset(elem);
2170
        if (includeMagins) {
2171
          var elemStyle = $window.getComputedStyle(elem);
2172
          elemOffset.top -= this.parseStyle(elemStyle.marginTop);
2173
          elemOffset.left -= this.parseStyle(elemStyle.marginLeft);
2174
        }
2175
        var parent = this.offsetParent(elem);
2176
        var parentOffset = {top: 0, left: 0};
2177
2178
        if (parent !== $document[0].documentElement) {
2179
          parentOffset = this.offset(parent);
2180
          parentOffset.top += parent.clientTop - parent.scrollTop;
2181
          parentOffset.left += parent.clientLeft - parent.scrollLeft;
2182
        }
2183
2184
        return {
2185
          width: Math.round(angular.isNumber(elemOffset.width) ? elemOffset.width : elem.offsetWidth),
2186
          height: Math.round(angular.isNumber(elemOffset.height) ? elemOffset.height : elem.offsetHeight),
2187
          top: Math.round(elemOffset.top - parentOffset.top),
2188
          left: Math.round(elemOffset.left - parentOffset.left)
2189
        };
2190
      },
2191
2192
      /**
2193
       * Provides read-only equivalent of jQuery's offset function:
2194
       * http://api.jquery.com/offset/ - distance to viewport.  Does
2195
       * not account for borders, margins, or padding on the body
2196
       * element.
2197
       *
2198
       * @param {element} elem - The element to calculate the offset on.
2199
       *
2200
       * @returns {object} An object with the following properties:
2201
       *   <ul>
2202
       *     <li>**width**: the width of the element</li>
2203
       *     <li>**height**: the height of the element</li>
2204
       *     <li>**top**: distance to top edge of viewport</li>
2205
       *     <li>**right**: distance to bottom edge of viewport</li>
2206
       *   </ul>
2207
       */
2208
      offset: function(elem) {
2209
        elem = this.getRawNode(elem);
2210
2211
        var elemBCR = elem.getBoundingClientRect();
2212
        return {
2213
          width: Math.round(angular.isNumber(elemBCR.width) ? elemBCR.width : elem.offsetWidth),
2214
          height: Math.round(angular.isNumber(elemBCR.height) ? elemBCR.height : elem.offsetHeight),
2215
          top: Math.round(elemBCR.top + ($window.pageYOffset || $document[0].documentElement.scrollTop)),
2216
          left: Math.round(elemBCR.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft))
2217
        };
2218
      },
2219
2220
      /**
2221
       * Provides offset distance to the closest scrollable ancestor
2222
       * or viewport.  Accounts for border and scrollbar width.
2223
       *
2224
       * Right and bottom dimensions represent the distance to the
2225
       * respective edge of the viewport element.  If the element
2226
       * edge extends beyond the viewport, a negative value will be
2227
       * reported.
2228
       *
2229
       * @param {element} elem - The element to get the viewport offset for.
2230
       * @param {boolean=} [useDocument=false] - Should the viewport be the document element instead
2231
       * of the first scrollable element, default is false.
2232
       * @param {boolean=} [includePadding=true] - Should the padding on the offset parent element
2233
       * be accounted for, default is true.
2234
       *
2235
       * @returns {object} An object with the following properties:
2236
       *   <ul>
2237
       *     <li>**top**: distance to the top content edge of viewport element</li>
2238
       *     <li>**bottom**: distance to the bottom content edge of viewport element</li>
2239
       *     <li>**left**: distance to the left content edge of viewport element</li>
2240
       *     <li>**right**: distance to the right content edge of viewport element</li>
2241
       *   </ul>
2242
       */
2243
      viewportOffset: function(elem, useDocument, includePadding) {
2244
        elem = this.getRawNode(elem);
2245
        includePadding = includePadding !== false ? true : false;
2246
2247
        var elemBCR = elem.getBoundingClientRect();
2248
        var offsetBCR = {top: 0, left: 0, bottom: 0, right: 0};
2249
2250
        var offsetParent = useDocument ? $document[0].documentElement : this.scrollParent(elem);
2251
        var offsetParentBCR = offsetParent.getBoundingClientRect();
2252
2253
        offsetBCR.top = offsetParentBCR.top + offsetParent.clientTop;
2254
        offsetBCR.left = offsetParentBCR.left + offsetParent.clientLeft;
2255
        if (offsetParent === $document[0].documentElement) {
2256
          offsetBCR.top += $window.pageYOffset;
2257
          offsetBCR.left += $window.pageXOffset;
2258
        }
2259
        offsetBCR.bottom = offsetBCR.top + offsetParent.clientHeight;
2260
        offsetBCR.right = offsetBCR.left + offsetParent.clientWidth;
2261
2262
        if (includePadding) {
2263
          var offsetParentStyle = $window.getComputedStyle(offsetParent);
2264
          offsetBCR.top += this.parseStyle(offsetParentStyle.paddingTop);
2265
          offsetBCR.bottom -= this.parseStyle(offsetParentStyle.paddingBottom);
2266
          offsetBCR.left += this.parseStyle(offsetParentStyle.paddingLeft);
2267
          offsetBCR.right -= this.parseStyle(offsetParentStyle.paddingRight);
2268
        }
2269
2270
        return {
2271
          top: Math.round(elemBCR.top - offsetBCR.top),
2272
          bottom: Math.round(offsetBCR.bottom - elemBCR.bottom),
2273
          left: Math.round(elemBCR.left - offsetBCR.left),
2274
          right: Math.round(offsetBCR.right - elemBCR.right)
2275
        };
2276
      },
2277
2278
      /**
2279
       * Provides an array of placement values parsed from a placement string.
2280
       * Along with the 'auto' indicator, supported placement strings are:
2281
       *   <ul>
2282
       *     <li>top: element on top, horizontally centered on host element.</li>
2283
       *     <li>top-left: element on top, left edge aligned with host element left edge.</li>
2284
       *     <li>top-right: element on top, lerightft edge aligned with host element right edge.</li>
2285
       *     <li>bottom: element on bottom, horizontally centered on host element.</li>
2286
       *     <li>bottom-left: element on bottom, left edge aligned with host element left edge.</li>
2287
       *     <li>bottom-right: element on bottom, right edge aligned with host element right edge.</li>
2288
       *     <li>left: element on left, vertically centered on host element.</li>
2289
       *     <li>left-top: element on left, top edge aligned with host element top edge.</li>
2290
       *     <li>left-bottom: element on left, bottom edge aligned with host element bottom edge.</li>
2291
       *     <li>right: element on right, vertically centered on host element.</li>
2292
       *     <li>right-top: element on right, top edge aligned with host element top edge.</li>
2293
       *     <li>right-bottom: element on right, bottom edge aligned with host element bottom edge.</li>
2294
       *   </ul>
2295
       * A placement string with an 'auto' indicator is expected to be
2296
       * space separated from the placement, i.e: 'auto bottom-left'  If
2297
       * the primary and secondary placement values do not match 'top,
2298
       * bottom, left, right' then 'top' will be the primary placement and
2299
       * 'center' will be the secondary placement.  If 'auto' is passed, true
2300
       * will be returned as the 3rd value of the array.
2301
       *
2302
       * @param {string} placement - The placement string to parse.
2303
       *
2304
       * @returns {array} An array with the following values
2305
       * <ul>
2306
       *   <li>**[0]**: The primary placement.</li>
2307
       *   <li>**[1]**: The secondary placement.</li>
2308
       *   <li>**[2]**: If auto is passed: true, else undefined.</li>
2309
       * </ul>
2310
       */
2311
      parsePlacement: function(placement) {
2312
        var autoPlace = PLACEMENT_REGEX.auto.test(placement);
2313
        if (autoPlace) {
2314
          placement = placement.replace(PLACEMENT_REGEX.auto, '');
2315
        }
2316
2317
        placement = placement.split('-');
2318
2319
        placement[0] = placement[0] || 'top';
2320
        if (!PLACEMENT_REGEX.primary.test(placement[0])) {
2321
          placement[0] = 'top';
2322
        }
2323
2324
        placement[1] = placement[1] || 'center';
2325
        if (!PLACEMENT_REGEX.secondary.test(placement[1])) {
2326
          placement[1] = 'center';
2327
        }
2328
2329
        if (autoPlace) {
2330
          placement[2] = true;
2331
        } else {
2332
          placement[2] = false;
2333
        }
2334
2335
        return placement;
2336
      },
2337
2338
      /**
2339
       * Provides coordinates for an element to be positioned relative to
2340
       * another element.  Passing 'auto' as part of the placement parameter
2341
       * will enable smart placement - where the element fits. i.e:
2342
       * 'auto left-top' will check to see if there is enough space to the left
2343
       * of the hostElem to fit the targetElem, if not place right (same for secondary
2344
       * top placement).  Available space is calculated using the viewportOffset
2345
       * function.
2346
       *
2347
       * @param {element} hostElem - The element to position against.
2348
       * @param {element} targetElem - The element to position.
2349
       * @param {string=} [placement=top] - The placement for the targetElem,
2350
       *   default is 'top'. 'center' is assumed as secondary placement for
2351
       *   'top', 'left', 'right', and 'bottom' placements.  Available placements are:
2352
       *   <ul>
2353
       *     <li>top</li>
2354
       *     <li>top-right</li>
2355
       *     <li>top-left</li>
2356
       *     <li>bottom</li>
2357
       *     <li>bottom-left</li>
2358
       *     <li>bottom-right</li>
2359
       *     <li>left</li>
2360
       *     <li>left-top</li>
2361
       *     <li>left-bottom</li>
2362
       *     <li>right</li>
2363
       *     <li>right-top</li>
2364
       *     <li>right-bottom</li>
2365
       *   </ul>
2366
       * @param {boolean=} [appendToBody=false] - Should the top and left values returned
2367
       *   be calculated from the body element, default is false.
2368
       *
2369
       * @returns {object} An object with the following properties:
2370
       *   <ul>
2371
       *     <li>**top**: Value for targetElem top.</li>
2372
       *     <li>**left**: Value for targetElem left.</li>
2373
       *     <li>**placement**: The resolved placement.</li>
2374
       *   </ul>
2375
       */
2376
      positionElements: function(hostElem, targetElem, placement, appendToBody) {
2377
        hostElem = this.getRawNode(hostElem);
2378
        targetElem = this.getRawNode(targetElem);
2379
2380
        // need to read from prop to support tests.
2381
        var targetWidth = angular.isDefined(targetElem.offsetWidth) ? targetElem.offsetWidth : targetElem.prop('offsetWidth');
2382
        var targetHeight = angular.isDefined(targetElem.offsetHeight) ? targetElem.offsetHeight : targetElem.prop('offsetHeight');
2383
2384
        placement = this.parsePlacement(placement);
2385
2386
        var hostElemPos = appendToBody ? this.offset(hostElem) : this.position(hostElem);
2387
        var targetElemPos = {top: 0, left: 0, placement: ''};
2388
2389
        if (placement[2]) {
2390
          var viewportOffset = this.viewportOffset(hostElem, appendToBody);
2391
2392
          var targetElemStyle = $window.getComputedStyle(targetElem);
2393
          var adjustedSize = {
2394
            width: targetWidth + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginLeft) + this.parseStyle(targetElemStyle.marginRight))),
2395
            height: targetHeight + Math.round(Math.abs(this.parseStyle(targetElemStyle.marginTop) + this.parseStyle(targetElemStyle.marginBottom)))
2396
          };
2397
2398
          placement[0] = placement[0] === 'top' && adjustedSize.height > viewportOffset.top && adjustedSize.height <= viewportOffset.bottom ? 'bottom' :
2399
                         placement[0] === 'bottom' && adjustedSize.height > viewportOffset.bottom && adjustedSize.height <= viewportOffset.top ? 'top' :
2400
                         placement[0] === 'left' && adjustedSize.width > viewportOffset.left && adjustedSize.width <= viewportOffset.right ? 'right' :
2401
                         placement[0] === 'right' && adjustedSize.width > viewportOffset.right && adjustedSize.width <= viewportOffset.left ? 'left' :
2402
                         placement[0];
2403
2404
          placement[1] = placement[1] === 'top' && adjustedSize.height - hostElemPos.height > viewportOffset.bottom && adjustedSize.height - hostElemPos.height <= viewportOffset.top ? 'bottom' :
2405
                         placement[1] === 'bottom' && adjustedSize.height - hostElemPos.height > viewportOffset.top && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom ? 'top' :
2406
                         placement[1] === 'left' && adjustedSize.width - hostElemPos.width > viewportOffset.right && adjustedSize.width - hostElemPos.width <= viewportOffset.left ? 'right' :
2407
                         placement[1] === 'right' && adjustedSize.width - hostElemPos.width > viewportOffset.left && adjustedSize.width - hostElemPos.width <= viewportOffset.right ? 'left' :
2408
                         placement[1];
2409
2410
          if (placement[1] === 'center') {
2411
            if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2412
              var xOverflow = hostElemPos.width / 2 - targetWidth / 2;
2413
              if (viewportOffset.left + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.right) {
2414
                placement[1] = 'left';
2415
              } else if (viewportOffset.right + xOverflow < 0 && adjustedSize.width - hostElemPos.width <= viewportOffset.left) {
2416
                placement[1] = 'right';
2417
              }
2418
            } else {
2419
              var yOverflow = hostElemPos.height / 2 - adjustedSize.height / 2;
2420
              if (viewportOffset.top + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.bottom) {
2421
                placement[1] = 'top';
2422
              } else if (viewportOffset.bottom + yOverflow < 0 && adjustedSize.height - hostElemPos.height <= viewportOffset.top) {
2423
                placement[1] = 'bottom';
2424
              }
2425
            }
2426
          }
2427
        }
2428
2429
        switch (placement[0]) {
2430
          case 'top':
2431
            targetElemPos.top = hostElemPos.top - targetHeight;
2432
            break;
2433
          case 'bottom':
2434
            targetElemPos.top = hostElemPos.top + hostElemPos.height;
2435
            break;
2436
          case 'left':
2437
            targetElemPos.left = hostElemPos.left - targetWidth;
2438
            break;
2439
          case 'right':
2440
            targetElemPos.left = hostElemPos.left + hostElemPos.width;
2441
            break;
2442
        }
2443
2444
        switch (placement[1]) {
2445
          case 'top':
2446
            targetElemPos.top = hostElemPos.top;
2447
            break;
2448
          case 'bottom':
2449
            targetElemPos.top = hostElemPos.top + hostElemPos.height - targetHeight;
2450
            break;
2451
          case 'left':
2452
            targetElemPos.left = hostElemPos.left;
2453
            break;
2454
          case 'right':
2455
            targetElemPos.left = hostElemPos.left + hostElemPos.width - targetWidth;
2456
            break;
2457
          case 'center':
2458
            if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2459
              targetElemPos.left = hostElemPos.left + hostElemPos.width / 2 - targetWidth / 2;
2460
            } else {
2461
              targetElemPos.top = hostElemPos.top + hostElemPos.height / 2 - targetHeight / 2;
2462
            }
2463
            break;
2464
        }
2465
2466
        targetElemPos.top = Math.round(targetElemPos.top);
2467
        targetElemPos.left = Math.round(targetElemPos.left);
2468
        targetElemPos.placement = placement[1] === 'center' ? placement[0] : placement[0] + '-' + placement[1];
2469
2470
        return targetElemPos;
2471
      },
2472
2473
      /**
2474
      * Provides a way for positioning tooltip & dropdown
2475
      * arrows when using placement options beyond the standard
2476
      * left, right, top, or bottom.
2477
      *
2478
      * @param {element} elem - The tooltip/dropdown element.
2479
      * @param {string} placement - The placement for the elem.
2480
      */
2481
      positionArrow: function(elem, placement) {
2482
        elem = this.getRawNode(elem);
2483
2484
        var innerElem = elem.querySelector('.tooltip-inner, .popover-inner');
2485
        if (!innerElem) {
2486
          return;
2487
        }
2488
2489
        var isTooltip = angular.element(innerElem).hasClass('tooltip-inner');
2490
2491
        var arrowElem = isTooltip ? elem.querySelector('.tooltip-arrow') : elem.querySelector('.arrow');
2492
        if (!arrowElem) {
2493
          return;
2494
        }
2495
2496
        var arrowCss = {
2497
          top: '',
2498
          bottom: '',
2499
          left: '',
2500
          right: ''
2501
        };
2502
2503
        placement = this.parsePlacement(placement);
2504
        if (placement[1] === 'center') {
2505
          // no adjustment necessary - just reset styles
2506
          angular.element(arrowElem).css(arrowCss);
2507
          return;
2508
        }
2509
2510
        var borderProp = 'border-' + placement[0] + '-width';
2511
        var borderWidth = $window.getComputedStyle(arrowElem)[borderProp];
2512
2513
        var borderRadiusProp = 'border-';
2514
        if (PLACEMENT_REGEX.vertical.test(placement[0])) {
2515
          borderRadiusProp += placement[0] + '-' + placement[1];
2516
        } else {
2517
          borderRadiusProp += placement[1] + '-' + placement[0];
2518
        }
2519
        borderRadiusProp += '-radius';
2520
        var borderRadius = $window.getComputedStyle(isTooltip ? innerElem : elem)[borderRadiusProp];
2521
2522
        switch (placement[0]) {
2523
          case 'top':
2524
            arrowCss.bottom = isTooltip ? '0' : '-' + borderWidth;
2525
            break;
2526
          case 'bottom':
2527
            arrowCss.top = isTooltip ? '0' : '-' + borderWidth;
2528
            break;
2529
          case 'left':
2530
            arrowCss.right = isTooltip ? '0' : '-' + borderWidth;
2531
            break;
2532
          case 'right':
2533
            arrowCss.left = isTooltip ? '0' : '-' + borderWidth;
2534
            break;
2535
        }
2536
2537
        arrowCss[placement[1]] = borderRadius;
2538
2539
        angular.element(arrowElem).css(arrowCss);
2540
      }
2541
    };
2542
  }]);
2543
2544
angular.module('ui.bootstrap.datepickerPopup', ['ui.bootstrap.datepicker', 'ui.bootstrap.position'])
2545
2546
.value('$datepickerPopupLiteralWarning', true)
2547
2548
.constant('uibDatepickerPopupConfig', {
2549
  altInputFormats: [],
2550
  appendToBody: false,
2551
  clearText: 'Clear',
2552
  closeOnDateSelection: true,
2553
  closeText: 'Done',
2554
  currentText: 'Today',
2555
  datepickerPopup: 'yyyy-MM-dd',
2556
  datepickerPopupTemplateUrl: 'uib/template/datepickerPopup/popup.html',
2557
  datepickerTemplateUrl: 'uib/template/datepicker/datepicker.html',
2558
  html5Types: {
2559
    date: 'yyyy-MM-dd',
2560
    'datetime-local': 'yyyy-MM-ddTHH:mm:ss.sss',
2561
    'month': 'yyyy-MM'
2562
  },
2563
  onOpenFocus: true,
2564
  showButtonBar: true,
2565
  placement: 'auto bottom-left'
2566
})
2567
2568
.controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning',
2569 View Code Duplication
function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) {
2570
  var cache = {},
0 ignored issues
show
Unused Code introduced by
The variable cache seems to be never used. Consider removing it.
Loading history...
2571
    isHtml5DateInput = false;
2572
  var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus,
2573
    datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl,
2574
    ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [],
2575
    timezone;
2576
2577
  this.init = function(_ngModel_) {
2578
    ngModel = _ngModel_;
2579
    ngModelOptions = _ngModel_.$options;
2580
    closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ?
2581
      $scope.$parent.$eval($attrs.closeOnDateSelection) :
2582
      datepickerPopupConfig.closeOnDateSelection;
2583
    appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ?
2584
      $scope.$parent.$eval($attrs.datepickerAppendToBody) :
2585
      datepickerPopupConfig.appendToBody;
2586
    onOpenFocus = angular.isDefined($attrs.onOpenFocus) ?
2587
      $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus;
2588
    datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ?
2589
      $attrs.datepickerPopupTemplateUrl :
2590
      datepickerPopupConfig.datepickerPopupTemplateUrl;
2591
    datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ?
2592
      $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl;
2593
    altInputFormats = angular.isDefined($attrs.altInputFormats) ?
2594
      $scope.$parent.$eval($attrs.altInputFormats) :
2595
      datepickerPopupConfig.altInputFormats;
2596
2597
    $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ?
2598
      $scope.$parent.$eval($attrs.showButtonBar) :
2599
      datepickerPopupConfig.showButtonBar;
2600
2601
    if (datepickerPopupConfig.html5Types[$attrs.type]) {
2602
      dateFormat = datepickerPopupConfig.html5Types[$attrs.type];
2603
      isHtml5DateInput = true;
2604
    } else {
2605
      dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup;
2606
      $attrs.$observe('uibDatepickerPopup', function(value, oldValue) {
2607
        var newDateFormat = value || datepickerPopupConfig.datepickerPopup;
2608
        // Invalidate the $modelValue to ensure that formatters re-run
2609
        // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764
2610
        if (newDateFormat !== dateFormat) {
2611
          dateFormat = newDateFormat;
2612
          ngModel.$modelValue = null;
2613
2614
          if (!dateFormat) {
2615
            throw new Error('uibDatepickerPopup must have a date format specified.');
2616
          }
2617
        }
2618
      });
2619
    }
2620
2621
    if (!dateFormat) {
2622
      throw new Error('uibDatepickerPopup must have a date format specified.');
2623
    }
2624
2625
    if (isHtml5DateInput && $attrs.uibDatepickerPopup) {
2626
      throw new Error('HTML5 date input types do not support custom formats.');
2627
    }
2628
2629
    // popup element used to display calendar
2630
    popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>');
2631
    if (ngModelOptions) {
2632
      timezone = ngModelOptions.timezone;
2633
      $scope.ngModelOptions = angular.copy(ngModelOptions);
2634
      $scope.ngModelOptions.timezone = null;
2635
      if ($scope.ngModelOptions.updateOnDefault === true) {
2636
        $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ?
2637
          $scope.ngModelOptions.updateOn + ' default' : 'default';
2638
      }
2639
2640
      popupEl.attr('ng-model-options', 'ngModelOptions');
2641
    } else {
2642
      timezone = null;
2643
    }
2644
2645
    popupEl.attr({
2646
      'ng-model': 'date',
2647
      'ng-change': 'dateSelection(date)',
2648
      'template-url': datepickerPopupTemplateUrl
2649
    });
2650
2651
    // datepicker element
2652
    datepickerEl = angular.element(popupEl.children()[0]);
2653
    datepickerEl.attr('template-url', datepickerTemplateUrl);
2654
2655
    if (!$scope.datepickerOptions) {
2656
      $scope.datepickerOptions = {};
2657
    }
2658
2659
    if (isHtml5DateInput) {
2660
      if ($attrs.type === 'month') {
2661
        $scope.datepickerOptions.datepickerMode = 'month';
2662
        $scope.datepickerOptions.minMode = 'month';
2663
      }
2664
    }
2665
2666
    datepickerEl.attr('datepicker-options', 'datepickerOptions');
2667
2668
    if (!isHtml5DateInput) {
2669
      // Internal API to maintain the correct ng-invalid-[key] class
2670
      ngModel.$$parserName = 'date';
2671
      ngModel.$validators.date = validator;
2672
      ngModel.$parsers.unshift(parseDate);
2673
      ngModel.$formatters.push(function(value) {
2674
        if (ngModel.$isEmpty(value)) {
2675
          $scope.date = value;
2676
          return value;
2677
        }
2678
2679
        $scope.date = dateParser.fromTimezone(value, timezone);
2680
2681
        if (angular.isNumber($scope.date)) {
2682
          $scope.date = new Date($scope.date);
2683
        }
2684
2685
        return dateParser.filter($scope.date, dateFormat);
2686
      });
2687
    } else {
2688
      ngModel.$formatters.push(function(value) {
2689
        $scope.date = dateParser.fromTimezone(value, timezone);
2690
        return value;
2691
      });
2692
    }
2693
2694
    // Detect changes in the view from the text box
2695
    ngModel.$viewChangeListeners.push(function() {
2696
      $scope.date = parseDateString(ngModel.$viewValue);
2697
    });
2698
2699
    $element.on('keydown', inputKeydownBind);
2700
2701
    $popup = $compile(popupEl)($scope);
2702
    // Prevent jQuery cache memory leak (template is now redundant after linking)
2703
    popupEl.remove();
2704
2705
    if (appendToBody) {
2706
      $document.find('body').append($popup);
2707
    } else {
2708
      $element.after($popup);
2709
    }
2710
2711
    $scope.$on('$destroy', function() {
2712
      if ($scope.isOpen === true) {
2713
        if (!$rootScope.$$phase) {
2714
          $scope.$apply(function() {
2715
            $scope.isOpen = false;
2716
          });
2717
        }
2718
      }
2719
2720
      $popup.remove();
2721
      $element.off('keydown', inputKeydownBind);
2722
      $document.off('click', documentClickBind);
2723
      if (scrollParentEl) {
2724
        scrollParentEl.off('scroll', positionPopup);
2725
      }
2726
      angular.element($window).off('resize', positionPopup);
2727
2728
      //Clear all watch listeners on destroy
2729
      while (watchListeners.length) {
2730
        watchListeners.shift()();
2731
      }
2732
    });
2733
  };
2734
2735
  $scope.getText = function(key) {
2736
    return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text'];
2737
  };
2738
2739
  $scope.isDisabled = function(date) {
2740
    if (date === 'today') {
2741
      date = dateParser.fromTimezone(new Date(), timezone);
2742
    }
2743
2744
    var dates = {};
2745
    angular.forEach(['minDate', 'maxDate'], function(key) {
2746
      if (!$scope.datepickerOptions[key]) {
2747
        dates[key] = null;
2748
      } else if (angular.isDate($scope.datepickerOptions[key])) {
2749
        dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone);
2750
      } else {
2751
        if ($datepickerPopupLiteralWarning) {
2752
          $log.warn('Literal date support has been deprecated, please switch to date object usage');
2753
        }
2754
2755
        dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium'));
2756
      }
2757
    });
2758
2759
    return $scope.datepickerOptions &&
2760
      dates.minDate && $scope.compare(date, dates.minDate) < 0 ||
2761
      dates.maxDate && $scope.compare(date, dates.maxDate) > 0;
2762
  };
2763
2764
  $scope.compare = function(date1, date2) {
2765
    return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate());
2766
  };
2767
2768
  // Inner change
2769
  $scope.dateSelection = function(dt) {
2770
    if (angular.isDefined(dt)) {
2771
      $scope.date = dt;
2772
    }
2773
    var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function
2774
    $element.val(date);
2775
    ngModel.$setViewValue(date);
2776
2777
    if (closeOnDateSelection) {
2778
      $scope.isOpen = false;
2779
      $element[0].focus();
2780
    }
2781
  };
2782
2783
  $scope.keydown = function(evt) {
2784
    if (evt.which === 27) {
2785
      evt.stopPropagation();
2786
      $scope.isOpen = false;
2787
      $element[0].focus();
2788
    }
2789
  };
2790
2791
  $scope.select = function(date, evt) {
2792
    evt.stopPropagation();
2793
2794
    if (date === 'today') {
2795
      var today = new Date();
2796
      if (angular.isDate($scope.date)) {
2797
        date = new Date($scope.date);
2798
        date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate());
2799
      } else {
2800
        date = new Date(today.setHours(0, 0, 0, 0));
2801
      }
2802
    }
2803
    $scope.dateSelection(date);
2804
  };
2805
2806
  $scope.close = function(evt) {
2807
    evt.stopPropagation();
2808
2809
    $scope.isOpen = false;
2810
    $element[0].focus();
2811
  };
2812
2813
  $scope.disabled = angular.isDefined($attrs.disabled) || false;
2814
  if ($attrs.ngDisabled) {
2815
    watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) {
2816
      $scope.disabled = disabled;
2817
    }));
2818
  }
2819
2820
  $scope.$watch('isOpen', function(value) {
2821
    if (value) {
2822
      if (!$scope.disabled) {
2823
        $timeout(function() {
2824
          positionPopup();
2825
2826
          if (onOpenFocus) {
2827
            $scope.$broadcast('uib:datepicker.focus');
2828
          }
2829
2830
          $document.on('click', documentClickBind);
2831
2832
          var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
2833
          if (appendToBody || $position.parsePlacement(placement)[2]) {
2834
            scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element));
2835
            if (scrollParentEl) {
2836
              scrollParentEl.on('scroll', positionPopup);
2837
            }
2838
          } else {
2839
            scrollParentEl = null;
2840
          }
2841
2842
          angular.element($window).on('resize', positionPopup);
2843
        }, 0, false);
2844
      } else {
2845
        $scope.isOpen = false;
2846
      }
2847
    } else {
2848
      $document.off('click', documentClickBind);
2849
      if (scrollParentEl) {
2850
        scrollParentEl.off('scroll', positionPopup);
2851
      }
2852
      angular.element($window).off('resize', positionPopup);
2853
    }
2854
  });
2855
2856
  function cameltoDash(string) {
0 ignored issues
show
introduced by
The function cameltoDash does not seem to be used and can be removed.
Loading history...
2857
    return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); });
2858
  }
2859
2860
  function parseDateString(viewValue) {
2861
    var date = dateParser.parse(viewValue, dateFormat, $scope.date);
2862
    if (isNaN(date)) {
2863
      for (var i = 0; i < altInputFormats.length; i++) {
2864
        date = dateParser.parse(viewValue, altInputFormats[i], $scope.date);
2865
        if (!isNaN(date)) {
2866
          return date;
2867
        }
2868
      }
2869
    }
2870
    return date;
2871
  }
2872
2873
  function parseDate(viewValue) {
2874
    if (angular.isNumber(viewValue)) {
2875
      // presumably timestamp to date object
2876
      viewValue = new Date(viewValue);
2877
    }
2878
2879
    if (!viewValue) {
2880
      return null;
2881
    }
2882
2883
    if (angular.isDate(viewValue) && !isNaN(viewValue)) {
2884
      return viewValue;
2885
    }
2886
2887
    if (angular.isString(viewValue)) {
2888
      var date = parseDateString(viewValue);
2889
      if (!isNaN(date)) {
2890
        return dateParser.toTimezone(date, timezone);
2891
      }
2892
    }
2893
2894
    return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined;
2895
  }
2896
2897
  function validator(modelValue, viewValue) {
2898
    var value = modelValue || viewValue;
2899
2900
    if (!$attrs.ngRequired && !value) {
2901
      return true;
2902
    }
2903
2904
    if (angular.isNumber(value)) {
2905
      value = new Date(value);
2906
    }
2907
2908
    if (!value) {
2909
      return true;
2910
    }
2911
2912
    if (angular.isDate(value) && !isNaN(value)) {
2913
      return true;
2914
    }
2915
2916
    if (angular.isString(value)) {
2917
      return !isNaN(parseDateString(viewValue));
2918
    }
2919
2920
    return false;
2921
  }
2922
2923
  function documentClickBind(event) {
2924
    if (!$scope.isOpen && $scope.disabled) {
2925
      return;
2926
    }
2927
2928
    var popup = $popup[0];
2929
    var dpContainsTarget = $element[0].contains(event.target);
2930
    // The popup node may not be an element node
2931
    // In some browsers (IE) only element nodes have the 'contains' function
2932
    var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target);
2933
    if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) {
2934
      $scope.$apply(function() {
2935
        $scope.isOpen = false;
2936
      });
2937
    }
2938
  }
2939
2940
  function inputKeydownBind(evt) {
2941
    if (evt.which === 27 && $scope.isOpen) {
2942
      evt.preventDefault();
2943
      evt.stopPropagation();
2944
      $scope.$apply(function() {
2945
        $scope.isOpen = false;
2946
      });
2947
      $element[0].focus();
2948
    } else if (evt.which === 40 && !$scope.isOpen) {
2949
      evt.preventDefault();
2950
      evt.stopPropagation();
2951
      $scope.$apply(function() {
2952
        $scope.isOpen = true;
2953
      });
2954
    }
2955
  }
2956
2957
  function positionPopup() {
2958
    if ($scope.isOpen) {
2959
      var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup'));
2960
      var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement;
2961
      var position = $position.positionElements($element, dpElement, placement, appendToBody);
2962
      dpElement.css({top: position.top + 'px', left: position.left + 'px'});
2963
      if (dpElement.hasClass('uib-position-measure')) {
2964
        dpElement.removeClass('uib-position-measure');
2965
      }
2966
    }
2967
  }
2968
2969
  $scope.$on('uib:datepicker.mode', function() {
2970
    $timeout(positionPopup, 0, false);
2971
  });
2972
}])
2973
2974
.directive('uibDatepickerPopup', function() {
2975
  return {
2976
    require: ['ngModel', 'uibDatepickerPopup'],
2977
    controller: 'UibDatepickerPopupController',
2978
    scope: {
2979
      datepickerOptions: '=?',
2980
      isOpen: '=?',
2981
      currentText: '@',
2982
      clearText: '@',
2983
      closeText: '@'
2984
    },
2985
    link: function(scope, element, attrs, ctrls) {
2986
      var ngModel = ctrls[0],
2987
        ctrl = ctrls[1];
2988
2989
      ctrl.init(ngModel);
2990
    }
2991
  };
2992
})
2993
2994
.directive('uibDatepickerPopupWrap', function() {
2995
  return {
2996
    replace: true,
2997
    transclude: true,
2998
    templateUrl: function(element, attrs) {
2999
      return attrs.templateUrl || 'uib/template/datepickerPopup/popup.html';
3000
    }
3001
  };
3002
});
3003
3004
angular.module('ui.bootstrap.debounce', [])
3005
/**
3006
 * A helper, internal service that debounces a function
3007
 */
3008
  .factory('$$debounce', ['$timeout', function($timeout) {
3009
    return function(callback, debounceTime) {
3010
      var timeoutPromise;
3011
3012
      return function() {
3013
        var self = this;
3014
        var args = Array.prototype.slice.call(arguments);
3015
        if (timeoutPromise) {
3016
          $timeout.cancel(timeoutPromise);
3017
        }
3018
3019
        timeoutPromise = $timeout(function() {
3020
          callback.apply(self, args);
3021
        }, debounceTime);
3022
      };
3023
    };
3024
  }]);
3025
3026
angular.module('ui.bootstrap.dropdown', ['ui.bootstrap.position'])
3027
3028
.constant('uibDropdownConfig', {
3029
  appendToOpenClass: 'uib-dropdown-open',
3030
  openClass: 'open'
3031
})
3032
3033 View Code Duplication
.service('uibDropdownService', ['$document', '$rootScope', function($document, $rootScope) {
3034
  var openScope = null;
3035
3036
  this.open = function(dropdownScope, element) {
3037
    if (!openScope) {
3038
      $document.on('click', closeDropdown);
3039
      element.on('keydown', keybindFilter);
3040
    }
3041
3042
    if (openScope && openScope !== dropdownScope) {
3043
      openScope.isOpen = false;
3044
    }
3045
3046
    openScope = dropdownScope;
3047
  };
3048
3049
  this.close = function(dropdownScope, element) {
3050
    if (openScope === dropdownScope) {
3051
      openScope = null;
3052
      $document.off('click', closeDropdown);
3053
      element.off('keydown', keybindFilter);
3054
    }
3055
  };
3056
3057
  var closeDropdown = function(evt) {
3058
    // This method may still be called during the same mouse event that
3059
    // unbound this event handler. So check openScope before proceeding.
3060
    if (!openScope) { return; }
3061
3062
    if (evt && openScope.getAutoClose() === 'disabled') { return; }
3063
3064
    if (evt && evt.which === 3) { return; }
3065
3066
    var toggleElement = openScope.getToggleElement();
3067
    if (evt && toggleElement && toggleElement[0].contains(evt.target)) {
3068
      return;
3069
    }
3070
3071
    var dropdownElement = openScope.getDropdownElement();
3072
    if (evt && openScope.getAutoClose() === 'outsideClick' &&
3073
      dropdownElement && dropdownElement[0].contains(evt.target)) {
3074
      return;
3075
    }
3076
3077
    openScope.isOpen = false;
3078
3079
    if (!$rootScope.$$phase) {
3080
      openScope.$apply();
3081
    }
3082
  };
3083
3084
  var keybindFilter = function(evt) {
3085
    if (evt.which === 27) {
3086
      evt.stopPropagation();
3087
      openScope.focusToggleElement();
3088
      closeDropdown();
3089
    } else if (openScope.isKeynavEnabled() && [38, 40].indexOf(evt.which) !== -1 && openScope.isOpen) {
3090
      evt.preventDefault();
3091
      evt.stopPropagation();
3092
      openScope.focusDropdownEntry(evt.which);
3093
    }
3094
  };
3095
}])
3096
3097 View Code Duplication
.controller('UibDropdownController', ['$scope', '$element', '$attrs', '$parse', 'uibDropdownConfig', 'uibDropdownService', '$animate', '$uibPosition', '$document', '$compile', '$templateRequest', function($scope, $element, $attrs, $parse, dropdownConfig, uibDropdownService, $animate, $position, $document, $compile, $templateRequest) {
3098
  var self = this,
3099
    scope = $scope.$new(), // create a child scope so we are not polluting original one
3100
    templateScope,
3101
    appendToOpenClass = dropdownConfig.appendToOpenClass,
3102
    openClass = dropdownConfig.openClass,
3103
    getIsOpen,
3104
    setIsOpen = angular.noop,
3105
    toggleInvoker = $attrs.onToggle ? $parse($attrs.onToggle) : angular.noop,
3106
    appendToBody = false,
3107
    appendTo = null,
3108
    keynavEnabled = false,
3109
    selectedOption = null,
3110
    body = $document.find('body');
3111
3112
  $element.addClass('dropdown');
3113
3114
  this.init = function() {
3115
    if ($attrs.isOpen) {
3116
      getIsOpen = $parse($attrs.isOpen);
3117
      setIsOpen = getIsOpen.assign;
3118
3119
      $scope.$watch(getIsOpen, function(value) {
3120
        scope.isOpen = !!value;
3121
      });
3122
    }
3123
3124
    if (angular.isDefined($attrs.dropdownAppendTo)) {
3125
      var appendToEl = $parse($attrs.dropdownAppendTo)(scope);
3126
      if (appendToEl) {
3127
        appendTo = angular.element(appendToEl);
3128
      }
3129
    }
3130
3131
    appendToBody = angular.isDefined($attrs.dropdownAppendToBody);
3132
    keynavEnabled = angular.isDefined($attrs.keyboardNav);
3133
3134
    if (appendToBody && !appendTo) {
3135
      appendTo = body;
3136
    }
3137
3138
    if (appendTo && self.dropdownMenu) {
3139
      appendTo.append(self.dropdownMenu);
3140
      $element.on('$destroy', function handleDestroyEvent() {
3141
        self.dropdownMenu.remove();
3142
      });
3143
    }
3144
  };
3145
3146
  this.toggle = function(open) {
3147
    scope.isOpen = arguments.length ? !!open : !scope.isOpen;
3148
    if (angular.isFunction(setIsOpen)) {
3149
      setIsOpen(scope, scope.isOpen);
3150
    }
3151
3152
    return scope.isOpen;
3153
  };
3154
3155
  // Allow other directives to watch status
3156
  this.isOpen = function() {
3157
    return scope.isOpen;
3158
  };
3159
3160
  scope.getToggleElement = function() {
3161
    return self.toggleElement;
3162
  };
3163
3164
  scope.getAutoClose = function() {
3165
    return $attrs.autoClose || 'always'; //or 'outsideClick' or 'disabled'
3166
  };
3167
3168
  scope.getElement = function() {
3169
    return $element;
3170
  };
3171
3172
  scope.isKeynavEnabled = function() {
3173
    return keynavEnabled;
3174
  };
3175
3176
  scope.focusDropdownEntry = function(keyCode) {
3177
    var elems = self.dropdownMenu ? //If append to body is used.
3178
      angular.element(self.dropdownMenu).find('a') :
3179
      $element.find('ul').eq(0).find('a');
3180
3181
    switch (keyCode) {
3182
      case 40: {
3183
        if (!angular.isNumber(self.selectedOption)) {
3184
          self.selectedOption = 0;
3185
        } else {
3186
          self.selectedOption = self.selectedOption === elems.length - 1 ?
3187
            self.selectedOption :
3188
            self.selectedOption + 1;
3189
        }
3190
        break;
3191
      }
3192
      case 38: {
3193
        if (!angular.isNumber(self.selectedOption)) {
3194
          self.selectedOption = elems.length - 1;
3195
        } else {
3196
          self.selectedOption = self.selectedOption === 0 ?
3197
            0 : self.selectedOption - 1;
3198
        }
3199
        break;
3200
      }
3201
    }
3202
    elems[self.selectedOption].focus();
3203
  };
3204
3205
  scope.getDropdownElement = function() {
3206
    return self.dropdownMenu;
3207
  };
3208
3209
  scope.focusToggleElement = function() {
3210
    if (self.toggleElement) {
3211
      self.toggleElement[0].focus();
3212
    }
3213
  };
3214
3215
  scope.$watch('isOpen', function(isOpen, wasOpen) {
3216
    if (appendTo && self.dropdownMenu) {
3217
      var pos = $position.positionElements($element, self.dropdownMenu, 'bottom-left', true),
3218
        css,
3219
        rightalign;
3220
3221
      css = {
3222
        top: pos.top + 'px',
3223
        display: isOpen ? 'block' : 'none'
3224
      };
3225
3226
      rightalign = self.dropdownMenu.hasClass('dropdown-menu-right');
3227
      if (!rightalign) {
3228
        css.left = pos.left + 'px';
3229
        css.right = 'auto';
3230
      } else {
3231
        css.left = 'auto';
3232
        css.right = window.innerWidth -
3233
          (pos.left + $element.prop('offsetWidth')) + 'px';
3234
      }
3235
3236
      // Need to adjust our positioning to be relative to the appendTo container
3237
      // if it's not the body element
3238
      if (!appendToBody) {
3239
        var appendOffset = $position.offset(appendTo);
3240
3241
        css.top = pos.top - appendOffset.top + 'px';
3242
3243
        if (!rightalign) {
3244
          css.left = pos.left - appendOffset.left + 'px';
3245
        } else {
3246
          css.right = window.innerWidth -
3247
            (pos.left - appendOffset.left + $element.prop('offsetWidth')) + 'px';
3248
        }
3249
      }
3250
3251
      self.dropdownMenu.css(css);
3252
    }
3253
3254
    var openContainer = appendTo ? appendTo : $element;
3255
    var hasOpenClass = openContainer.hasClass(appendTo ? appendToOpenClass : openClass);
3256
3257
    if (hasOpenClass === !isOpen) {
3258
      $animate[isOpen ? 'addClass' : 'removeClass'](openContainer, appendTo ? appendToOpenClass : openClass).then(function() {
3259
        if (angular.isDefined(isOpen) && isOpen !== wasOpen) {
3260
          toggleInvoker($scope, { open: !!isOpen });
3261
        }
3262
      });
3263
    }
3264
3265
    if (isOpen) {
3266
      if (self.dropdownMenuTemplateUrl) {
3267
        $templateRequest(self.dropdownMenuTemplateUrl).then(function(tplContent) {
3268
          templateScope = scope.$new();
3269
          $compile(tplContent.trim())(templateScope, function(dropdownElement) {
3270
            var newEl = dropdownElement;
3271
            self.dropdownMenu.replaceWith(newEl);
3272
            self.dropdownMenu = newEl;
3273
          });
3274
        });
3275
      }
3276
3277
      scope.focusToggleElement();
3278
      uibDropdownService.open(scope, $element);
3279
    } else {
3280
      if (self.dropdownMenuTemplateUrl) {
3281
        if (templateScope) {
3282
          templateScope.$destroy();
3283
        }
3284
        var newEl = angular.element('<ul class="dropdown-menu"></ul>');
3285
        self.dropdownMenu.replaceWith(newEl);
3286
        self.dropdownMenu = newEl;
3287
      }
3288
3289
      uibDropdownService.close(scope, $element);
3290
      self.selectedOption = null;
3291
    }
3292
3293
    if (angular.isFunction(setIsOpen)) {
3294
      setIsOpen($scope, isOpen);
3295
    }
3296
  });
3297
}])
3298
3299
.directive('uibDropdown', function() {
3300
  return {
3301
    controller: 'UibDropdownController',
3302
    link: function(scope, element, attrs, dropdownCtrl) {
3303
      dropdownCtrl.init();
3304
    }
3305
  };
3306
})
3307
3308 View Code Duplication
.directive('uibDropdownMenu', function() {
3309
  return {
3310
    restrict: 'A',
3311
    require: '?^uibDropdown',
3312
    link: function(scope, element, attrs, dropdownCtrl) {
3313
      if (!dropdownCtrl || angular.isDefined(attrs.dropdownNested)) {
3314
        return;
3315
      }
3316
3317
      element.addClass('dropdown-menu');
3318
3319
      var tplUrl = attrs.templateUrl;
3320
      if (tplUrl) {
3321
        dropdownCtrl.dropdownMenuTemplateUrl = tplUrl;
3322
      }
3323
3324
      if (!dropdownCtrl.dropdownMenu) {
3325
        dropdownCtrl.dropdownMenu = element;
3326
      }
3327
    }
3328
  };
3329
})
3330
3331 View Code Duplication
.directive('uibDropdownToggle', function() {
3332
  return {
3333
    require: '?^uibDropdown',
3334
    link: function(scope, element, attrs, dropdownCtrl) {
3335
      if (!dropdownCtrl) {
3336
        return;
3337
      }
3338
3339
      element.addClass('dropdown-toggle');
3340
3341
      dropdownCtrl.toggleElement = element;
3342
3343
      var toggleDropdown = function(event) {
3344
        event.preventDefault();
3345
3346
        if (!element.hasClass('disabled') && !attrs.disabled) {
3347
          scope.$apply(function() {
3348
            dropdownCtrl.toggle();
3349
          });
3350
        }
3351
      };
3352
3353
      element.bind('click', toggleDropdown);
3354
3355
      // WAI-ARIA
3356
      element.attr({ 'aria-haspopup': true, 'aria-expanded': false });
3357
      scope.$watch(dropdownCtrl.isOpen, function(isOpen) {
3358
        element.attr('aria-expanded', !!isOpen);
3359
      });
3360
3361
      scope.$on('$destroy', function() {
3362
        element.unbind('click', toggleDropdown);
3363
      });
3364
    }
3365
  };
3366
});
3367
3368
angular.module('ui.bootstrap.stackedMap', [])
3369
/**
3370
 * A helper, internal data structure that acts as a map but also allows getting / removing
3371
 * elements in the LIFO order
3372
 */
3373 View Code Duplication
  .factory('$$stackedMap', function() {
3374
    return {
3375
      createNew: function() {
3376
        var stack = [];
3377
3378
        return {
3379
          add: function(key, value) {
3380
            stack.push({
3381
              key: key,
3382
              value: value
3383
            });
3384
          },
3385
          get: function(key) {
3386
            for (var i = 0; i < stack.length; i++) {
3387
              if (key === stack[i].key) {
3388
                return stack[i];
3389
              }
3390
            }
3391
          },
3392
          keys: function() {
3393
            var keys = [];
3394
            for (var i = 0; i < stack.length; i++) {
3395
              keys.push(stack[i].key);
3396
            }
3397
            return keys;
3398
          },
3399
          top: function() {
3400
            return stack[stack.length - 1];
3401
          },
3402
          remove: function(key) {
3403
            var idx = -1;
3404
            for (var i = 0; i < stack.length; i++) {
3405
              if (key === stack[i].key) {
3406
                idx = i;
3407
                break;
3408
              }
3409
            }
3410
            return stack.splice(idx, 1)[0];
3411
          },
3412
          removeTop: function() {
3413
            return stack.splice(stack.length - 1, 1)[0];
3414
          },
3415
          length: function() {
3416
            return stack.length;
3417
          }
3418
        };
3419
      }
3420
    };
3421
  });
3422
angular.module('ui.bootstrap.modal', ['ui.bootstrap.stackedMap', 'ui.bootstrap.position'])
3423
/**
3424
 * A helper, internal data structure that stores all references attached to key
3425
 */
3426 View Code Duplication
  .factory('$$multiMap', function() {
3427
    return {
3428
      createNew: function() {
3429
        var map = {};
3430
3431
        return {
3432
          entries: function() {
3433
            return Object.keys(map).map(function(key) {
3434
              return {
3435
                key: key,
3436
                value: map[key]
3437
              };
3438
            });
3439
          },
3440
          get: function(key) {
3441
            return map[key];
3442
          },
3443
          hasKey: function(key) {
3444
            return !!map[key];
3445
          },
3446
          keys: function() {
3447
            return Object.keys(map);
3448
          },
3449
          put: function(key, value) {
3450
            if (!map[key]) {
3451
              map[key] = [];
3452
            }
3453
3454
            map[key].push(value);
3455
          },
3456
          remove: function(key, value) {
3457
            var values = map[key];
3458
3459
            if (!values) {
3460
              return;
3461
            }
3462
3463
            var idx = values.indexOf(value);
3464
3465
            if (idx !== -1) {
3466
              values.splice(idx, 1);
3467
            }
3468
3469
            if (!values.length) {
3470
              delete map[key];
3471
            }
3472
          }
3473
        };
3474
      }
3475
    };
3476
  })
3477
3478
/**
3479
 * Pluggable resolve mechanism for the modal resolve resolution
3480
 * Supports UI Router's $resolve service
3481
 */
3482 View Code Duplication
  .provider('$uibResolve', function() {
3483
    var resolve = this;
3484
    this.resolver = null;
3485
3486
    this.setResolver = function(resolver) {
3487
      this.resolver = resolver;
3488
    };
3489
3490
    this.$get = ['$injector', '$q', function($injector, $q) {
3491
      var resolver = resolve.resolver ? $injector.get(resolve.resolver) : null;
3492
      return {
3493
        resolve: function(invocables, locals, parent, self) {
3494
          if (resolver) {
3495
            return resolver.resolve(invocables, locals, parent, self);
3496
          }
3497
3498
          var promises = [];
3499
3500
          angular.forEach(invocables, function(value) {
3501
            if (angular.isFunction(value) || angular.isArray(value)) {
3502
              promises.push($q.resolve($injector.invoke(value)));
3503
            } else if (angular.isString(value)) {
3504
              promises.push($q.resolve($injector.get(value)));
3505
            } else {
3506
              promises.push($q.resolve(value));
3507
            }
3508
          });
3509
3510
          return $q.all(promises).then(function(resolves) {
3511
            var resolveObj = {};
3512
            var resolveIter = 0;
3513
            angular.forEach(invocables, function(value, key) {
3514
              resolveObj[key] = resolves[resolveIter++];
3515
            });
3516
3517
            return resolveObj;
3518
          });
3519
        }
3520
      };
3521
    }];
3522
  })
3523
3524
/**
3525
 * A helper directive for the $modal service. It creates a backdrop element.
3526
 */
3527
  .directive('uibModalBackdrop', ['$animate', '$injector', '$uibModalStack',
3528
  function($animate, $injector, $modalStack) {
3529
    return {
3530
      replace: true,
3531
      templateUrl: 'uib/template/modal/backdrop.html',
3532
      compile: function(tElement, tAttrs) {
3533
        tElement.addClass(tAttrs.backdropClass);
3534
        return linkFn;
3535
      }
3536
    };
3537
3538
    function linkFn(scope, element, attrs) {
3539
      if (attrs.modalInClass) {
3540
        $animate.addClass(element, attrs.modalInClass);
3541
3542
        scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3543
          var done = setIsAsync();
3544
          if (scope.modalOptions.animation) {
3545
            $animate.removeClass(element, attrs.modalInClass).then(done);
3546
          } else {
3547
            done();
3548
          }
3549
        });
3550
      }
3551
    }
3552
  }])
3553
3554
  .directive('uibModalWindow', ['$uibModalStack', '$q', '$animateCss', '$document',
3555 View Code Duplication
  function($modalStack, $q, $animateCss, $document) {
3556
    return {
3557
      scope: {
3558
        index: '@'
3559
      },
3560
      replace: true,
3561
      transclude: true,
3562
      templateUrl: function(tElement, tAttrs) {
3563
        return tAttrs.templateUrl || 'uib/template/modal/window.html';
3564
      },
3565
      link: function(scope, element, attrs) {
3566
        element.addClass(attrs.windowClass || '');
3567
        element.addClass(attrs.windowTopClass || '');
3568
        scope.size = attrs.size;
3569
3570
        scope.close = function(evt) {
3571
          var modal = $modalStack.getTop();
3572
          if (modal && modal.value.backdrop &&
3573
            modal.value.backdrop !== 'static' &&
3574
            evt.target === evt.currentTarget) {
3575
            evt.preventDefault();
3576
            evt.stopPropagation();
3577
            $modalStack.dismiss(modal.key, 'backdrop click');
3578
          }
3579
        };
3580
3581
        // moved from template to fix issue #2280
3582
        element.on('click', scope.close);
3583
3584
        // This property is only added to the scope for the purpose of detecting when this directive is rendered.
3585
        // We can detect that by using this property in the template associated with this directive and then use
3586
        // {@link Attribute#$observe} on it. For more details please see {@link TableColumnResize}.
3587
        scope.$isRendered = true;
3588
3589
        // Deferred object that will be resolved when this modal is render.
3590
        var modalRenderDeferObj = $q.defer();
3591
        // Observe function will be called on next digest cycle after compilation, ensuring that the DOM is ready.
3592
        // In order to use this way of finding whether DOM is ready, we need to observe a scope property used in modal's template.
3593
        attrs.$observe('modalRender', function(value) {
3594
          if (value === 'true') {
3595
            modalRenderDeferObj.resolve();
3596
          }
3597
        });
3598
3599
        modalRenderDeferObj.promise.then(function() {
3600
          var animationPromise = null;
3601
3602
          if (attrs.modalInClass) {
3603
            animationPromise = $animateCss(element, {
3604
              addClass: attrs.modalInClass
3605
            }).start();
3606
3607
            scope.$on($modalStack.NOW_CLOSING_EVENT, function(e, setIsAsync) {
3608
              var done = setIsAsync();
3609
              $animateCss(element, {
3610
                removeClass: attrs.modalInClass
3611
              }).start().then(done);
3612
            });
3613
          }
3614
3615
3616
          $q.when(animationPromise).then(function() {
3617
            // Notify {@link $modalStack} that modal is rendered.
3618
            var modal = $modalStack.getTop();
3619
            if (modal) {
3620
              $modalStack.modalRendered(modal.key);
3621
            }
3622
3623
            /**
3624
             * If something within the freshly-opened modal already has focus (perhaps via a
3625
             * directive that causes focus). then no need to try and focus anything.
3626
             */
3627
            if (!($document[0].activeElement && element[0].contains($document[0].activeElement))) {
3628
              var inputWithAutofocus = element[0].querySelector('[autofocus]');
3629
              /**
3630
               * Auto-focusing of a freshly-opened modal element causes any child elements
3631
               * with the autofocus attribute to lose focus. This is an issue on touch
3632
               * based devices which will show and then hide the onscreen keyboard.
3633
               * Attempts to refocus the autofocus element via JavaScript will not reopen
3634
               * the onscreen keyboard. Fixed by updated the focusing logic to only autofocus
3635
               * the modal element if the modal does not contain an autofocus element.
3636
               */
3637
              if (inputWithAutofocus) {
3638
                inputWithAutofocus.focus();
3639
              } else {
3640
                element[0].focus();
3641
              }
3642
            }
3643
          });
3644
        });
3645
      }
3646
    };
3647
  }])
3648
3649
  .directive('uibModalAnimationClass', function() {
3650
    return {
3651
      compile: function(tElement, tAttrs) {
3652
        if (tAttrs.modalAnimation) {
3653
          tElement.addClass(tAttrs.uibModalAnimationClass);
3654
        }
3655
      }
3656
    };
3657
  })
3658
3659
  .directive('uibModalTransclude', function() {
3660
    return {
3661
      link: function(scope, element, attrs, controller, transclude) {
3662
        transclude(scope.$parent, function(clone) {
3663
          element.empty();
3664
          element.append(clone);
3665
        });
3666
      }
3667
    };
3668
  })
3669
3670
  .factory('$uibModalStack', ['$animate', '$animateCss', '$document',
3671
    '$compile', '$rootScope', '$q', '$$multiMap', '$$stackedMap', '$uibPosition',
3672 View Code Duplication
    function($animate, $animateCss, $document, $compile, $rootScope, $q, $$multiMap, $$stackedMap, $uibPosition) {
3673
      var OPENED_MODAL_CLASS = 'modal-open';
3674
3675
      var backdropDomEl, backdropScope;
3676
      var openedWindows = $$stackedMap.createNew();
3677
      var openedClasses = $$multiMap.createNew();
3678
      var $modalStack = {
3679
        NOW_CLOSING_EVENT: 'modal.stack.now-closing'
3680
      };
3681
      var topModalIndex = 0;
3682
      var previousTopOpenedModal = null;
3683
3684
      //Modal focus behavior
3685
      var tabableSelector = 'a[href], area[href], input:not([disabled]), ' +
3686
        'button:not([disabled]),select:not([disabled]), textarea:not([disabled]), ' +
3687
        'iframe, object, embed, *[tabindex], *[contenteditable=true]';
3688
      var scrollbarPadding;
3689
3690
      function isVisible(element) {
3691
        return !!(element.offsetWidth ||
3692
          element.offsetHeight ||
3693
          element.getClientRects().length);
3694
      }
3695
3696
      function backdropIndex() {
3697
        var topBackdropIndex = -1;
3698
        var opened = openedWindows.keys();
3699
        for (var i = 0; i < opened.length; i++) {
3700
          if (openedWindows.get(opened[i]).value.backdrop) {
3701
            topBackdropIndex = i;
3702
          }
3703
        }
3704
3705
        // If any backdrop exist, ensure that it's index is always
3706
        // right below the top modal
3707
        if (topBackdropIndex > -1 && topBackdropIndex < topModalIndex) {
3708
          topBackdropIndex = topModalIndex;
3709
        }
3710
        return topBackdropIndex;
3711
      }
3712
3713
      $rootScope.$watch(backdropIndex, function(newBackdropIndex) {
3714
        if (backdropScope) {
3715
          backdropScope.index = newBackdropIndex;
3716
        }
3717
      });
3718
3719
      function removeModalWindow(modalInstance, elementToReceiveFocus) {
3720
        var modalWindow = openedWindows.get(modalInstance).value;
3721
        var appendToElement = modalWindow.appendTo;
3722
3723
        //clean up the stack
3724
        openedWindows.remove(modalInstance);
3725
        previousTopOpenedModal = openedWindows.top();
3726
        if (previousTopOpenedModal) {
3727
          topModalIndex = parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10);
3728
        }
3729
3730
        removeAfterAnimate(modalWindow.modalDomEl, modalWindow.modalScope, function() {
3731
          var modalBodyClass = modalWindow.openedClass || OPENED_MODAL_CLASS;
3732
          openedClasses.remove(modalBodyClass, modalInstance);
3733
          var areAnyOpen = openedClasses.hasKey(modalBodyClass);
3734
          appendToElement.toggleClass(modalBodyClass, areAnyOpen);
3735
          if (!areAnyOpen && scrollbarPadding && scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
3736
            if (scrollbarPadding.originalRight) {
3737
              appendToElement.css({paddingRight: scrollbarPadding.originalRight + 'px'});
3738
            } else {
3739
              appendToElement.css({paddingRight: ''});
3740
            }
3741
            scrollbarPadding = null;
3742
          }
3743
          toggleTopWindowClass(true);
3744
        }, modalWindow.closedDeferred);
3745
        checkRemoveBackdrop();
3746
3747
        //move focus to specified element if available, or else to body
3748
        if (elementToReceiveFocus && elementToReceiveFocus.focus) {
3749
          elementToReceiveFocus.focus();
3750
        } else if (appendToElement.focus) {
3751
          appendToElement.focus();
3752
        }
3753
      }
3754
3755
      // Add or remove "windowTopClass" from the top window in the stack
3756
      function toggleTopWindowClass(toggleSwitch) {
3757
        var modalWindow;
3758
3759
        if (openedWindows.length() > 0) {
3760
          modalWindow = openedWindows.top().value;
3761
          modalWindow.modalDomEl.toggleClass(modalWindow.windowTopClass || '', toggleSwitch);
3762
        }
3763
      }
3764
3765
      function checkRemoveBackdrop() {
3766
        //remove backdrop if no longer needed
3767
        if (backdropDomEl && backdropIndex() === -1) {
3768
          var backdropScopeRef = backdropScope;
0 ignored issues
show
Unused Code introduced by
The variable backdropScopeRef seems to be never used. Consider removing it.
Loading history...
3769
          removeAfterAnimate(backdropDomEl, backdropScope, function() {
3770
            backdropScopeRef = null;
0 ignored issues
show
Unused Code introduced by
The variable backdropScopeRef seems to be never used. Consider removing it.
Loading history...
3771
          });
3772
          backdropDomEl = undefined;
3773
          backdropScope = undefined;
3774
        }
3775
      }
3776
3777
      function removeAfterAnimate(domEl, scope, done, closedDeferred) {
3778
        var asyncDeferred;
3779
        var asyncPromise = null;
3780
        var setIsAsync = function() {
3781
          if (!asyncDeferred) {
3782
            asyncDeferred = $q.defer();
3783
            asyncPromise = asyncDeferred.promise;
3784
          }
3785
3786
          return function asyncDone() {
3787
            asyncDeferred.resolve();
3788
          };
3789
        };
3790
        scope.$broadcast($modalStack.NOW_CLOSING_EVENT, setIsAsync);
3791
3792
        // Note that it's intentional that asyncPromise might be null.
3793
        // That's when setIsAsync has not been called during the
3794
        // NOW_CLOSING_EVENT broadcast.
3795
        return $q.when(asyncPromise).then(afterAnimating);
3796
3797
        function afterAnimating() {
3798
          if (afterAnimating.done) {
3799
            return;
3800
          }
3801
          afterAnimating.done = true;
3802
3803
          $animate.leave(domEl).then(function() {
3804
            domEl.remove();
3805
            if (closedDeferred) {
3806
              closedDeferred.resolve();
3807
            }
3808
          });
3809
3810
          scope.$destroy();
3811
          if (done) {
3812
            done();
3813
          }
3814
        }
3815
      }
3816
3817
      $document.on('keydown', keydownListener);
3818
3819
      $rootScope.$on('$destroy', function() {
3820
        $document.off('keydown', keydownListener);
3821
      });
3822
3823
      function keydownListener(evt) {
3824
        if (evt.isDefaultPrevented()) {
3825
          return evt;
3826
        }
3827
3828
        var modal = openedWindows.top();
3829
        if (modal) {
3830
          switch (evt.which) {
3831
            case 27: {
3832
              if (modal.value.keyboard) {
3833
                evt.preventDefault();
3834
                $rootScope.$apply(function() {
3835
                  $modalStack.dismiss(modal.key, 'escape key press');
3836
                });
3837
              }
3838
              break;
3839
            }
3840
            case 9: {
3841
              var list = $modalStack.loadFocusElementList(modal);
3842
              var focusChanged = false;
3843
              if (evt.shiftKey) {
3844
                if ($modalStack.isFocusInFirstItem(evt, list) || $modalStack.isModalFocused(evt, modal)) {
3845
                  focusChanged = $modalStack.focusLastFocusableElement(list);
3846
                }
3847
              } else {
3848
                if ($modalStack.isFocusInLastItem(evt, list)) {
3849
                  focusChanged = $modalStack.focusFirstFocusableElement(list);
3850
                }
3851
              }
3852
3853
              if (focusChanged) {
3854
                evt.preventDefault();
3855
                evt.stopPropagation();
3856
              }
3857
3858
              break;
3859
            }
3860
          }
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
3861
        }
3862
      }
3863
3864
      $modalStack.open = function(modalInstance, modal) {
3865
        var modalOpener = $document[0].activeElement,
3866
          modalBodyClass = modal.openedClass || OPENED_MODAL_CLASS;
3867
3868
        toggleTopWindowClass(false);
3869
3870
        // Store the current top first, to determine what index we ought to use
3871
        // for the current top modal
3872
        previousTopOpenedModal = openedWindows.top();
3873
3874
        openedWindows.add(modalInstance, {
3875
          deferred: modal.deferred,
3876
          renderDeferred: modal.renderDeferred,
3877
          closedDeferred: modal.closedDeferred,
3878
          modalScope: modal.scope,
3879
          backdrop: modal.backdrop,
3880
          keyboard: modal.keyboard,
3881
          openedClass: modal.openedClass,
3882
          windowTopClass: modal.windowTopClass,
3883
          animation: modal.animation,
3884
          appendTo: modal.appendTo
3885
        });
3886
3887
        openedClasses.put(modalBodyClass, modalInstance);
3888
3889
        var appendToElement = modal.appendTo,
3890
            currBackdropIndex = backdropIndex();
3891
3892
        if (!appendToElement.length) {
3893
          throw new Error('appendTo element not found. Make sure that the element passed is in DOM.');
3894
        }
3895
3896
        if (currBackdropIndex >= 0 && !backdropDomEl) {
3897
          backdropScope = $rootScope.$new(true);
3898
          backdropScope.modalOptions = modal;
3899
          backdropScope.index = currBackdropIndex;
3900
          backdropDomEl = angular.element('<div uib-modal-backdrop="modal-backdrop"></div>');
3901
          backdropDomEl.attr('backdrop-class', modal.backdropClass);
3902
          if (modal.animation) {
3903
            backdropDomEl.attr('modal-animation', 'true');
3904
          }
3905
          $compile(backdropDomEl)(backdropScope);
3906
          $animate.enter(backdropDomEl, appendToElement);
3907
          scrollbarPadding = $uibPosition.scrollbarPadding(appendToElement);
3908
          if (scrollbarPadding.heightOverflow && scrollbarPadding.scrollbarWidth) {
3909
            appendToElement.css({paddingRight: scrollbarPadding.right + 'px'});
3910
          }
3911
        }
3912
3913
        // Set the top modal index based on the index of the previous top modal
3914
        topModalIndex = previousTopOpenedModal ? parseInt(previousTopOpenedModal.value.modalDomEl.attr('index'), 10) + 1 : 0;
3915
        var angularDomEl = angular.element('<div uib-modal-window="modal-window"></div>');
3916
        angularDomEl.attr({
3917
          'template-url': modal.windowTemplateUrl,
3918
          'window-class': modal.windowClass,
3919
          'window-top-class': modal.windowTopClass,
3920
          'size': modal.size,
3921
          'index': topModalIndex,
3922
          'animate': 'animate'
3923
        }).html(modal.content);
3924
        if (modal.animation) {
3925
          angularDomEl.attr('modal-animation', 'true');
3926
        }
3927
3928
        appendToElement.addClass(modalBodyClass);
3929
        $animate.enter($compile(angularDomEl)(modal.scope), appendToElement);
3930
3931
        openedWindows.top().value.modalDomEl = angularDomEl;
3932
        openedWindows.top().value.modalOpener = modalOpener;
3933
      };
3934
3935
      function broadcastClosing(modalWindow, resultOrReason, closing) {
3936
        return !modalWindow.value.modalScope.$broadcast('modal.closing', resultOrReason, closing).defaultPrevented;
3937
      }
3938
3939
      $modalStack.close = function(modalInstance, result) {
3940
        var modalWindow = openedWindows.get(modalInstance);
3941
        if (modalWindow && broadcastClosing(modalWindow, result, true)) {
3942
          modalWindow.value.modalScope.$$uibDestructionScheduled = true;
3943
          modalWindow.value.deferred.resolve(result);
3944
          removeModalWindow(modalInstance, modalWindow.value.modalOpener);
3945
          return true;
3946
        }
3947
        return !modalWindow;
3948
      };
3949
3950
      $modalStack.dismiss = function(modalInstance, reason) {
3951
        var modalWindow = openedWindows.get(modalInstance);
3952
        if (modalWindow && broadcastClosing(modalWindow, reason, false)) {
3953
          modalWindow.value.modalScope.$$uibDestructionScheduled = true;
3954
          modalWindow.value.deferred.reject(reason);
3955
          removeModalWindow(modalInstance, modalWindow.value.modalOpener);
3956
          return true;
3957
        }
3958
        return !modalWindow;
3959
      };
3960
3961
      $modalStack.dismissAll = function(reason) {
3962
        var topModal = this.getTop();
3963
        while (topModal && this.dismiss(topModal.key, reason)) {
3964
          topModal = this.getTop();
3965
        }
3966
      };
3967
3968
      $modalStack.getTop = function() {
3969
        return openedWindows.top();
3970
      };
3971
3972
      $modalStack.modalRendered = function(modalInstance) {
3973
        var modalWindow = openedWindows.get(modalInstance);
3974
        if (modalWindow) {
3975
          modalWindow.value.renderDeferred.resolve();
3976
        }
3977
      };
3978
3979
      $modalStack.focusFirstFocusableElement = function(list) {
3980
        if (list.length > 0) {
3981
          list[0].focus();
3982
          return true;
3983
        }
3984
        return false;
3985
      };
3986
3987
      $modalStack.focusLastFocusableElement = function(list) {
3988
        if (list.length > 0) {
3989
          list[list.length - 1].focus();
3990
          return true;
3991
        }
3992
        return false;
3993
      };
3994
3995
      $modalStack.isModalFocused = function(evt, modalWindow) {
3996
        if (evt && modalWindow) {
3997
          var modalDomEl = modalWindow.value.modalDomEl;
3998
          if (modalDomEl && modalDomEl.length) {
3999
            return (evt.target || evt.srcElement) === modalDomEl[0];
4000
          }
4001
        }
4002
        return false;
4003
      };
4004
4005
      $modalStack.isFocusInFirstItem = function(evt, list) {
4006
        if (list.length > 0) {
4007
          return (evt.target || evt.srcElement) === list[0];
4008
        }
4009
        return false;
4010
      };
4011
4012
      $modalStack.isFocusInLastItem = function(evt, list) {
4013
        if (list.length > 0) {
4014
          return (evt.target || evt.srcElement) === list[list.length - 1];
4015
        }
4016
        return false;
4017
      };
4018
4019
      $modalStack.loadFocusElementList = function(modalWindow) {
4020
        if (modalWindow) {
4021
          var modalDomE1 = modalWindow.value.modalDomEl;
4022
          if (modalDomE1 && modalDomE1.length) {
4023
            var elements = modalDomE1[0].querySelectorAll(tabableSelector);
4024
            return elements ?
4025
              Array.prototype.filter.call(elements, function(element) {
4026
                return isVisible(element);
4027
              }) : elements;
4028
          }
4029
        }
4030
      };
4031
4032
      return $modalStack;
4033
    }])
4034
4035 View Code Duplication
  .provider('$uibModal', function() {
4036
    var $modalProvider = {
4037
      options: {
4038
        animation: true,
4039
        backdrop: true, //can also be false or 'static'
4040
        keyboard: true
4041
      },
4042
      $get: ['$rootScope', '$q', '$document', '$templateRequest', '$controller', '$uibResolve', '$uibModalStack',
4043
        function ($rootScope, $q, $document, $templateRequest, $controller, $uibResolve, $modalStack) {
4044
          var $modal = {};
4045
4046
          function getTemplatePromise(options) {
4047
            return options.template ? $q.when(options.template) :
4048
              $templateRequest(angular.isFunction(options.templateUrl) ?
4049
                options.templateUrl() : options.templateUrl);
4050
          }
4051
4052
          var promiseChain = null;
4053
          $modal.getPromiseChain = function() {
4054
            return promiseChain;
4055
          };
4056
4057
          $modal.open = function(modalOptions) {
4058
            var modalResultDeferred = $q.defer();
4059
            var modalOpenedDeferred = $q.defer();
4060
            var modalClosedDeferred = $q.defer();
4061
            var modalRenderDeferred = $q.defer();
4062
4063
            //prepare an instance of a modal to be injected into controllers and returned to a caller
4064
            var modalInstance = {
4065
              result: modalResultDeferred.promise,
4066
              opened: modalOpenedDeferred.promise,
4067
              closed: modalClosedDeferred.promise,
4068
              rendered: modalRenderDeferred.promise,
4069
              close: function (result) {
4070
                return $modalStack.close(modalInstance, result);
4071
              },
4072
              dismiss: function (reason) {
4073
                return $modalStack.dismiss(modalInstance, reason);
4074
              }
4075
            };
4076
4077
            //merge and clean up options
4078
            modalOptions = angular.extend({}, $modalProvider.options, modalOptions);
4079
            modalOptions.resolve = modalOptions.resolve || {};
4080
            modalOptions.appendTo = modalOptions.appendTo || $document.find('body').eq(0);
4081
4082
            //verify options
4083
            if (!modalOptions.template && !modalOptions.templateUrl) {
4084
              throw new Error('One of template or templateUrl options is required.');
4085
            }
4086
4087
            var templateAndResolvePromise =
4088
              $q.all([getTemplatePromise(modalOptions), $uibResolve.resolve(modalOptions.resolve, {}, null, null)]);
4089
4090
            function resolveWithTemplate() {
4091
              return templateAndResolvePromise;
4092
            }
4093
4094
            // Wait for the resolution of the existing promise chain.
4095
            // Then switch to our own combined promise dependency (regardless of how the previous modal fared).
4096
            // Then add to $modalStack and resolve opened.
4097
            // Finally clean up the chain variable if no subsequent modal has overwritten it.
4098
            var samePromise;
4099
            samePromise = promiseChain = $q.all([promiseChain])
4100
              .then(resolveWithTemplate, resolveWithTemplate)
4101
              .then(function resolveSuccess(tplAndVars) {
4102
                var providedScope = modalOptions.scope || $rootScope;
4103
4104
                var modalScope = providedScope.$new();
4105
                modalScope.$close = modalInstance.close;
4106
                modalScope.$dismiss = modalInstance.dismiss;
4107
4108
                modalScope.$on('$destroy', function() {
4109
                  if (!modalScope.$$uibDestructionScheduled) {
4110
                    modalScope.$dismiss('$uibUnscheduledDestruction');
4111
                  }
4112
                });
4113
4114
                var ctrlInstance, ctrlInstantiate, ctrlLocals = {};
4115
4116
                //controllers
4117
                if (modalOptions.controller) {
4118
                  ctrlLocals.$scope = modalScope;
4119
                  ctrlLocals.$uibModalInstance = modalInstance;
4120
                  angular.forEach(tplAndVars[1], function(value, key) {
4121
                    ctrlLocals[key] = value;
4122
                  });
4123
4124
                  // the third param will make the controller instantiate later,private api
4125
                  // @see https://github.com/angular/angular.js/blob/master/src/ng/controller.js#L126
4126
                  ctrlInstantiate = $controller(modalOptions.controller, ctrlLocals, true);
4127
                  if (modalOptions.controllerAs) {
4128
                    ctrlInstance = ctrlInstantiate.instance;
4129
4130
                    if (modalOptions.bindToController) {
4131
                      ctrlInstance.$close = modalScope.$close;
4132
                      ctrlInstance.$dismiss = modalScope.$dismiss;
4133
                      angular.extend(ctrlInstance, providedScope);
4134
                    }
4135
4136
                    ctrlInstance = ctrlInstantiate();
4137
4138
                    modalScope[modalOptions.controllerAs] = ctrlInstance;
4139
                  } else {
4140
                    ctrlInstance = ctrlInstantiate();
4141
                  }
4142
4143
                  if (angular.isFunction(ctrlInstance.$onInit)) {
4144
                    ctrlInstance.$onInit();
4145
                  }
4146
                }
4147
4148
                $modalStack.open(modalInstance, {
4149
                  scope: modalScope,
4150
                  deferred: modalResultDeferred,
4151
                  renderDeferred: modalRenderDeferred,
4152
                  closedDeferred: modalClosedDeferred,
4153
                  content: tplAndVars[0],
4154
                  animation: modalOptions.animation,
4155
                  backdrop: modalOptions.backdrop,
4156
                  keyboard: modalOptions.keyboard,
4157
                  backdropClass: modalOptions.backdropClass,
4158
                  windowTopClass: modalOptions.windowTopClass,
4159
                  windowClass: modalOptions.windowClass,
4160
                  windowTemplateUrl: modalOptions.windowTemplateUrl,
4161
                  size: modalOptions.size,
4162
                  openedClass: modalOptions.openedClass,
4163
                  appendTo: modalOptions.appendTo
4164
                });
4165
                modalOpenedDeferred.resolve(true);
4166
4167
            }, function resolveError(reason) {
4168
              modalOpenedDeferred.reject(reason);
4169
              modalResultDeferred.reject(reason);
4170
            })['finally'](function() {
4171
              if (promiseChain === samePromise) {
4172
                promiseChain = null;
4173
              }
4174
            });
4175
4176
            return modalInstance;
4177
          };
4178
4179
          return $modal;
4180
        }
4181
      ]
4182
    };
4183
4184
    return $modalProvider;
4185
  });
4186
4187
angular.module('ui.bootstrap.paging', [])
4188
/**
4189
 * Helper internal service for generating common controller code between the
4190
 * pager and pagination components
4191
 */
4192 View Code Duplication
.factory('uibPaging', ['$parse', function($parse) {
4193
  return {
4194
    create: function(ctrl, $scope, $attrs) {
4195
      ctrl.setNumPages = $attrs.numPages ? $parse($attrs.numPages).assign : angular.noop;
4196
      ctrl.ngModelCtrl = { $setViewValue: angular.noop }; // nullModelCtrl
4197
      ctrl._watchers = [];
4198
4199
      ctrl.init = function(ngModelCtrl, config) {
4200
        ctrl.ngModelCtrl = ngModelCtrl;
4201
        ctrl.config = config;
4202
4203
        ngModelCtrl.$render = function() {
4204
          ctrl.render();
4205
        };
4206
4207
        if ($attrs.itemsPerPage) {
4208
          ctrl._watchers.push($scope.$parent.$watch($attrs.itemsPerPage, function(value) {
4209
            ctrl.itemsPerPage = parseInt(value, 10);
4210
            $scope.totalPages = ctrl.calculateTotalPages();
4211
            ctrl.updatePage();
4212
          }));
4213
        } else {
4214
          ctrl.itemsPerPage = config.itemsPerPage;
4215
        }
4216
4217
        $scope.$watch('totalItems', function(newTotal, oldTotal) {
4218
          if (angular.isDefined(newTotal) || newTotal !== oldTotal) {
4219
            $scope.totalPages = ctrl.calculateTotalPages();
4220
            ctrl.updatePage();
4221
          }
4222
        });
4223
      };
4224
4225
      ctrl.calculateTotalPages = function() {
4226
        var totalPages = ctrl.itemsPerPage < 1 ? 1 : Math.ceil($scope.totalItems / ctrl.itemsPerPage);
4227
        return Math.max(totalPages || 0, 1);
4228
      };
4229
4230
      ctrl.render = function() {
4231
        $scope.page = parseInt(ctrl.ngModelCtrl.$viewValue, 10) || 1;
4232
      };
4233
4234
      $scope.selectPage = function(page, evt) {
4235
        if (evt) {
4236
          evt.preventDefault();
4237
        }
4238
4239
        var clickAllowed = !$scope.ngDisabled || !evt;
4240
        if (clickAllowed && $scope.page !== page && page > 0 && page <= $scope.totalPages) {
4241
          if (evt && evt.target) {
4242
            evt.target.blur();
4243
          }
4244
          ctrl.ngModelCtrl.$setViewValue(page);
4245
          ctrl.ngModelCtrl.$render();
4246
        }
4247
      };
4248
4249
      $scope.getText = function(key) {
4250
        return $scope[key + 'Text'] || ctrl.config[key + 'Text'];
4251
      };
4252
4253
      $scope.noPrevious = function() {
4254
        return $scope.page === 1;
4255
      };
4256
4257
      $scope.noNext = function() {
4258
        return $scope.page === $scope.totalPages;
4259
      };
4260
4261
      ctrl.updatePage = function() {
4262
        ctrl.setNumPages($scope.$parent, $scope.totalPages); // Readonly variable
4263
4264
        if ($scope.page > $scope.totalPages) {
4265
          $scope.selectPage($scope.totalPages);
4266
        } else {
4267
          ctrl.ngModelCtrl.$render();
4268
        }
4269
      };
4270
4271
      $scope.$on('$destroy', function() {
4272
        while (ctrl._watchers.length) {
4273
          ctrl._watchers.shift()();
4274
        }
4275
      });
4276
    }
4277
  };
4278
}]);
4279
4280
angular.module('ui.bootstrap.pager', ['ui.bootstrap.paging'])
4281
4282
.controller('UibPagerController', ['$scope', '$attrs', 'uibPaging', 'uibPagerConfig', function($scope, $attrs, uibPaging, uibPagerConfig) {
4283
  $scope.align = angular.isDefined($attrs.align) ? $scope.$parent.$eval($attrs.align) : uibPagerConfig.align;
4284
4285
  uibPaging.create(this, $scope, $attrs);
4286
}])
4287
4288
.constant('uibPagerConfig', {
4289
  itemsPerPage: 10,
4290
  previousText: '« Previous',
4291
  nextText: 'Next »',
4292
  align: true
4293
})
4294
4295 View Code Duplication
.directive('uibPager', ['uibPagerConfig', function(uibPagerConfig) {
4296
  return {
4297
    scope: {
4298
      totalItems: '=',
4299
      previousText: '@',
4300
      nextText: '@',
4301
      ngDisabled: '='
4302
    },
4303
    require: ['uibPager', '?ngModel'],
4304
    controller: 'UibPagerController',
4305
    controllerAs: 'pager',
4306
    templateUrl: function(element, attrs) {
4307
      return attrs.templateUrl || 'uib/template/pager/pager.html';
4308
    },
4309
    replace: true,
4310
    link: function(scope, element, attrs, ctrls) {
4311
      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4312
4313
      if (!ngModelCtrl) {
4314
        return; // do nothing if no ng-model
4315
      }
4316
4317
      paginationCtrl.init(ngModelCtrl, uibPagerConfig);
4318
    }
4319
  };
4320
}]);
4321
4322
angular.module('ui.bootstrap.pagination', ['ui.bootstrap.paging'])
4323 View Code Duplication
.controller('UibPaginationController', ['$scope', '$attrs', '$parse', 'uibPaging', 'uibPaginationConfig', function($scope, $attrs, $parse, uibPaging, uibPaginationConfig) {
4324
  var ctrl = this;
4325
  // Setup configuration parameters
4326
  var maxSize = angular.isDefined($attrs.maxSize) ? $scope.$parent.$eval($attrs.maxSize) : uibPaginationConfig.maxSize,
4327
    rotate = angular.isDefined($attrs.rotate) ? $scope.$parent.$eval($attrs.rotate) : uibPaginationConfig.rotate,
4328
    forceEllipses = angular.isDefined($attrs.forceEllipses) ? $scope.$parent.$eval($attrs.forceEllipses) : uibPaginationConfig.forceEllipses,
4329
    boundaryLinkNumbers = angular.isDefined($attrs.boundaryLinkNumbers) ? $scope.$parent.$eval($attrs.boundaryLinkNumbers) : uibPaginationConfig.boundaryLinkNumbers,
4330
    pageLabel = angular.isDefined($attrs.pageLabel) ? function(idx) { return $scope.$parent.$eval($attrs.pageLabel, {$page: idx}); } : angular.identity;
4331
  $scope.boundaryLinks = angular.isDefined($attrs.boundaryLinks) ? $scope.$parent.$eval($attrs.boundaryLinks) : uibPaginationConfig.boundaryLinks;
4332
  $scope.directionLinks = angular.isDefined($attrs.directionLinks) ? $scope.$parent.$eval($attrs.directionLinks) : uibPaginationConfig.directionLinks;
4333
4334
  uibPaging.create(this, $scope, $attrs);
4335
4336
  if ($attrs.maxSize) {
4337
    ctrl._watchers.push($scope.$parent.$watch($parse($attrs.maxSize), function(value) {
4338
      maxSize = parseInt(value, 10);
4339
      ctrl.render();
4340
    }));
4341
  }
4342
4343
  // Create page object used in template
4344
  function makePage(number, text, isActive) {
4345
    return {
4346
      number: number,
4347
      text: text,
4348
      active: isActive
4349
    };
4350
  }
4351
4352
  function getPages(currentPage, totalPages) {
4353
    var pages = [];
4354
4355
    // Default page limits
4356
    var startPage = 1, endPage = totalPages;
4357
    var isMaxSized = angular.isDefined(maxSize) && maxSize < totalPages;
4358
4359
    // recompute if maxSize
4360
    if (isMaxSized) {
4361
      if (rotate) {
4362
        // Current page is displayed in the middle of the visible ones
4363
        startPage = Math.max(currentPage - Math.floor(maxSize / 2), 1);
4364
        endPage = startPage + maxSize - 1;
4365
4366
        // Adjust if limit is exceeded
4367
        if (endPage > totalPages) {
4368
          endPage = totalPages;
4369
          startPage = endPage - maxSize + 1;
4370
        }
4371
      } else {
4372
        // Visible pages are paginated with maxSize
4373
        startPage = (Math.ceil(currentPage / maxSize) - 1) * maxSize + 1;
4374
4375
        // Adjust last page if limit is exceeded
4376
        endPage = Math.min(startPage + maxSize - 1, totalPages);
4377
      }
4378
    }
4379
4380
    // Add page number links
4381
    for (var number = startPage; number <= endPage; number++) {
4382
      var page = makePage(number, pageLabel(number), number === currentPage);
4383
      pages.push(page);
4384
    }
4385
4386
    // Add links to move between page sets
4387
    if (isMaxSized && maxSize > 0 && (!rotate || forceEllipses || boundaryLinkNumbers)) {
4388
      if (startPage > 1) {
4389
        if (!boundaryLinkNumbers || startPage > 3) { //need ellipsis for all options unless range is too close to beginning
4390
        var previousPageSet = makePage(startPage - 1, '...', false);
4391
        pages.unshift(previousPageSet);
4392
      }
4393
        if (boundaryLinkNumbers) {
4394
          if (startPage === 3) { //need to replace ellipsis when the buttons would be sequential
4395
            var secondPageLink = makePage(2, '2', false);
4396
            pages.unshift(secondPageLink);
4397
          }
4398
          //add the first page
4399
          var firstPageLink = makePage(1, '1', false);
4400
          pages.unshift(firstPageLink);
4401
        }
4402
      }
4403
4404
      if (endPage < totalPages) {
4405
        if (!boundaryLinkNumbers || endPage < totalPages - 2) { //need ellipsis for all options unless range is too close to end
4406
        var nextPageSet = makePage(endPage + 1, '...', false);
4407
        pages.push(nextPageSet);
4408
      }
4409
        if (boundaryLinkNumbers) {
4410
          if (endPage === totalPages - 2) { //need to replace ellipsis when the buttons would be sequential
4411
            var secondToLastPageLink = makePage(totalPages - 1, totalPages - 1, false);
4412
            pages.push(secondToLastPageLink);
4413
          }
4414
          //add the last page
4415
          var lastPageLink = makePage(totalPages, totalPages, false);
4416
          pages.push(lastPageLink);
4417
        }
4418
      }
4419
    }
4420
    return pages;
4421
  }
4422
4423
  var originalRender = this.render;
4424
  this.render = function() {
4425
    originalRender();
4426
    if ($scope.page > 0 && $scope.page <= $scope.totalPages) {
4427
      $scope.pages = getPages($scope.page, $scope.totalPages);
4428
    }
4429
  };
4430
}])
4431
4432
.constant('uibPaginationConfig', {
4433
  itemsPerPage: 10,
4434
  boundaryLinks: false,
4435
  boundaryLinkNumbers: false,
4436
  directionLinks: true,
4437
  firstText: 'First',
4438
  previousText: 'Previous',
4439
  nextText: 'Next',
4440
  lastText: 'Last',
4441
  rotate: true,
4442
  forceEllipses: false
4443
})
4444
4445 View Code Duplication
.directive('uibPagination', ['$parse', 'uibPaginationConfig', function($parse, uibPaginationConfig) {
4446
  return {
4447
    scope: {
4448
      totalItems: '=',
4449
      firstText: '@',
4450
      previousText: '@',
4451
      nextText: '@',
4452
      lastText: '@',
4453
      ngDisabled:'='
4454
    },
4455
    require: ['uibPagination', '?ngModel'],
4456
    controller: 'UibPaginationController',
4457
    controllerAs: 'pagination',
4458
    templateUrl: function(element, attrs) {
4459
      return attrs.templateUrl || 'uib/template/pagination/pagination.html';
4460
    },
4461
    replace: true,
4462
    link: function(scope, element, attrs, ctrls) {
4463
      var paginationCtrl = ctrls[0], ngModelCtrl = ctrls[1];
4464
4465
      if (!ngModelCtrl) {
4466
         return; // do nothing if no ng-model
4467
      }
4468
4469
      paginationCtrl.init(ngModelCtrl, uibPaginationConfig);
4470
    }
4471
  };
4472
}]);
4473
4474
/**
4475
 * The following features are still outstanding: animation as a
4476
 * function, placement as a function, inside, support for more triggers than
4477
 * just mouse enter/leave, html tooltips, and selector delegation.
4478
 */
4479
angular.module('ui.bootstrap.tooltip', ['ui.bootstrap.position', 'ui.bootstrap.stackedMap'])
4480
4481
/**
4482
 * The $tooltip service creates tooltip- and popover-like directives as well as
4483
 * houses global options for them.
4484
 */
4485 View Code Duplication
.provider('$uibTooltip', function() {
4486
  // The default options tooltip and popover.
4487
  var defaultOptions = {
4488
    placement: 'top',
4489
    placementClassPrefix: '',
4490
    animation: true,
4491
    popupDelay: 0,
4492
    popupCloseDelay: 0,
4493
    useContentExp: false
4494
  };
4495
4496
  // Default hide triggers for each show trigger
4497
  var triggerMap = {
4498
    'mouseenter': 'mouseleave',
4499
    'click': 'click',
4500
    'outsideClick': 'outsideClick',
4501
    'focus': 'blur',
4502
    'none': ''
4503
  };
4504
4505
  // The options specified to the provider globally.
4506
  var globalOptions = {};
4507
4508
  /**
4509
   * `options({})` allows global configuration of all tooltips in the
4510
   * application.
4511
   *
4512
   *   var app = angular.module( 'App', ['ui.bootstrap.tooltip'], function( $tooltipProvider ) {
4513
   *     // place tooltips left instead of top by default
4514
   *     $tooltipProvider.options( { placement: 'left' } );
4515
   *   });
4516
   */
4517
	this.options = function(value) {
4518
		angular.extend(globalOptions, value);
4519
	};
4520
4521
  /**
4522
   * This allows you to extend the set of trigger mappings available. E.g.:
4523
   *
4524
   *   $tooltipProvider.setTriggers( { 'openTrigger': 'closeTrigger' } );
4525
   */
4526
  this.setTriggers = function setTriggers(triggers) {
4527
    angular.extend(triggerMap, triggers);
4528
  };
4529
4530
  /**
4531
   * This is a helper function for translating camel-case to snake_case.
4532
   */
4533
  function snake_case(name) {
4534
    var regexp = /[A-Z]/g;
4535
    var separator = '-';
4536
    return name.replace(regexp, function(letter, pos) {
4537
      return (pos ? separator : '') + letter.toLowerCase();
4538
    });
4539
  }
4540
4541
  /**
4542
   * Returns the actual instance of the $tooltip service.
4543
   * TODO support multiple triggers
4544
   */
4545
  this.$get = ['$window', '$compile', '$timeout', '$document', '$uibPosition', '$interpolate', '$rootScope', '$parse', '$$stackedMap', function($window, $compile, $timeout, $document, $position, $interpolate, $rootScope, $parse, $$stackedMap) {
4546
    var openedTooltips = $$stackedMap.createNew();
4547
    $document.on('keypress', keypressListener);
4548
4549
    $rootScope.$on('$destroy', function() {
4550
      $document.off('keypress', keypressListener);
4551
    });
4552
4553
    function keypressListener(e) {
4554
      if (e.which === 27) {
4555
        var last = openedTooltips.top();
4556
        if (last) {
4557
          last.value.close();
4558
          openedTooltips.removeTop();
4559
          last = null;
0 ignored issues
show
Unused Code introduced by
The assignment to last seems to be never used. If you intend to free memory here, this is not necessary since the variable leaves the scope anyway.
Loading history...
4560
        }
4561
      }
4562
    }
4563
4564
    return function $tooltip(ttType, prefix, defaultTriggerShow, options) {
4565
      options = angular.extend({}, defaultOptions, globalOptions, options);
4566
4567
      /**
4568
       * Returns an object of show and hide triggers.
4569
       *
4570
       * If a trigger is supplied,
4571
       * it is used to show the tooltip; otherwise, it will use the `trigger`
4572
       * option passed to the `$tooltipProvider.options` method; else it will
4573
       * default to the trigger supplied to this directive factory.
4574
       *
4575
       * The hide trigger is based on the show trigger. If the `trigger` option
4576
       * was passed to the `$tooltipProvider.options` method, it will use the
4577
       * mapped trigger from `triggerMap` or the passed trigger if the map is
4578
       * undefined; otherwise, it uses the `triggerMap` value of the show
4579
       * trigger; else it will just use the show trigger.
4580
       */
4581
      function getTriggers(trigger) {
4582
        var show = (trigger || options.trigger || defaultTriggerShow).split(' ');
4583
        var hide = show.map(function(trigger) {
4584
          return triggerMap[trigger] || trigger;
4585
        });
4586
        return {
4587
          show: show,
4588
          hide: hide
4589
        };
4590
      }
4591
4592
      var directiveName = snake_case(ttType);
4593
4594
      var startSym = $interpolate.startSymbol();
4595
      var endSym = $interpolate.endSymbol();
4596
      var template =
4597
        '<div '+ directiveName + '-popup ' +
4598
          'uib-title="' + startSym + 'title' + endSym + '" ' +
4599
          (options.useContentExp ?
4600
            'content-exp="contentExp()" ' :
4601
            'content="' + startSym + 'content' + endSym + '" ') +
4602
          'placement="' + startSym + 'placement' + endSym + '" ' +
4603
          'popup-class="' + startSym + 'popupClass' + endSym + '" ' +
4604
          'animation="animation" ' +
4605
          'is-open="isOpen" ' +
4606
          'origin-scope="origScope" ' +
4607
          'class="uib-position-measure"' +
4608
          '>' +
4609
        '</div>';
4610
4611
      return {
4612
        compile: function(tElem, tAttrs) {
4613
          var tooltipLinker = $compile(template);
4614
4615
          return function link(scope, element, attrs, tooltipCtrl) {
4616
            var tooltip;
4617
            var tooltipLinkedScope;
4618
            var transitionTimeout;
4619
            var showTimeout;
4620
            var hideTimeout;
4621
            var positionTimeout;
4622
            var appendToBody = angular.isDefined(options.appendToBody) ? options.appendToBody : false;
4623
            var triggers = getTriggers(undefined);
4624
            var hasEnableExp = angular.isDefined(attrs[prefix + 'Enable']);
4625
            var ttScope = scope.$new(true);
4626
            var repositionScheduled = false;
4627
            var isOpenParse = angular.isDefined(attrs[prefix + 'IsOpen']) ? $parse(attrs[prefix + 'IsOpen']) : false;
4628
            var contentParse = options.useContentExp ? $parse(attrs[ttType]) : false;
4629
            var observers = [];
4630
            var lastPlacement;
4631
4632
            var positionTooltip = function() {
4633
              // check if tooltip exists and is not empty
4634
              if (!tooltip || !tooltip.html()) { return; }
4635
4636
              if (!positionTimeout) {
4637
                positionTimeout = $timeout(function() {
4638
                  var ttPosition = $position.positionElements(element, tooltip, ttScope.placement, appendToBody);
4639
                  tooltip.css({ top: ttPosition.top + 'px', left: ttPosition.left + 'px' });
4640
4641
                  if (!tooltip.hasClass(ttPosition.placement.split('-')[0])) {
4642
                    tooltip.removeClass(lastPlacement.split('-')[0]);
4643
                    tooltip.addClass(ttPosition.placement.split('-')[0]);
4644
                  }
4645
4646
                  if (!tooltip.hasClass(options.placementClassPrefix + ttPosition.placement)) {
4647
                    tooltip.removeClass(options.placementClassPrefix + lastPlacement);
4648
                    tooltip.addClass(options.placementClassPrefix + ttPosition.placement);
4649
                  }
4650
4651
                  // first time through tt element will have the
4652
                  // uib-position-measure class or if the placement
4653
                  // has changed we need to position the arrow.
4654
                  if (tooltip.hasClass('uib-position-measure')) {
4655
                    $position.positionArrow(tooltip, ttPosition.placement);
4656
                    tooltip.removeClass('uib-position-measure');
4657
                  } else if (lastPlacement !== ttPosition.placement) {
4658
                    $position.positionArrow(tooltip, ttPosition.placement);
4659
                  }
4660
                  lastPlacement = ttPosition.placement;
4661
4662
                  positionTimeout = null;
4663
                }, 0, false);
4664
              }
4665
            };
4666
4667
            // Set up the correct scope to allow transclusion later
4668
            ttScope.origScope = scope;
4669
4670
            // By default, the tooltip is not open.
4671
            // TODO add ability to start tooltip opened
4672
            ttScope.isOpen = false;
4673
            openedTooltips.add(ttScope, {
4674
              close: hide
4675
            });
4676
4677
            function toggleTooltipBind() {
4678
              if (!ttScope.isOpen) {
4679
                showTooltipBind();
4680
              } else {
4681
                hideTooltipBind();
4682
              }
4683
            }
4684
4685
            // Show the tooltip with delay if specified, otherwise show it immediately
4686
            function showTooltipBind() {
4687
              if (hasEnableExp && !scope.$eval(attrs[prefix + 'Enable'])) {
4688
                return;
4689
              }
4690
4691
              cancelHide();
4692
              prepareTooltip();
4693
4694
              if (ttScope.popupDelay) {
4695
                // Do nothing if the tooltip was already scheduled to pop-up.
4696
                // This happens if show is triggered multiple times before any hide is triggered.
4697
                if (!showTimeout) {
4698
                  showTimeout = $timeout(show, ttScope.popupDelay, false);
4699
                }
4700
              } else {
4701
                show();
4702
              }
4703
            }
4704
4705
            function hideTooltipBind() {
4706
              cancelShow();
4707
4708
              if (ttScope.popupCloseDelay) {
4709
                if (!hideTimeout) {
4710
                  hideTimeout = $timeout(hide, ttScope.popupCloseDelay, false);
4711
                }
4712
              } else {
4713
                hide();
4714
              }
4715
            }
4716
4717
            // Show the tooltip popup element.
4718
            function show() {
4719
              cancelShow();
4720
              cancelHide();
4721
4722
              // Don't show empty tooltips.
4723
              if (!ttScope.content) {
4724
                return angular.noop;
4725
              }
4726
4727
              createTooltip();
4728
4729
              // And show the tooltip.
4730
              ttScope.$evalAsync(function() {
4731
                ttScope.isOpen = true;
4732
                assignIsOpen(true);
4733
                positionTooltip();
4734
              });
4735
            }
4736
4737
            function cancelShow() {
4738
              if (showTimeout) {
4739
                $timeout.cancel(showTimeout);
4740
                showTimeout = null;
4741
              }
4742
4743
              if (positionTimeout) {
4744
                $timeout.cancel(positionTimeout);
4745
                positionTimeout = null;
4746
              }
4747
            }
4748
4749
            // Hide the tooltip popup element.
4750
            function hide() {
4751
              if (!ttScope) {
4752
                return;
4753
              }
4754
4755
              // First things first: we don't show it anymore.
4756
              ttScope.$evalAsync(function() {
4757
                if (ttScope) {
4758
                  ttScope.isOpen = false;
4759
                  assignIsOpen(false);
4760
                  // And now we remove it from the DOM. However, if we have animation, we
4761
                  // need to wait for it to expire beforehand.
4762
                  // FIXME: this is a placeholder for a port of the transitions library.
4763
                  // The fade transition in TWBS is 150ms.
4764
                  if (ttScope.animation) {
4765
                    if (!transitionTimeout) {
4766
                      transitionTimeout = $timeout(removeTooltip, 150, false);
4767
                    }
4768
                  } else {
4769
                    removeTooltip();
4770
                  }
4771
                }
4772
              });
4773
            }
4774
4775
            function cancelHide() {
4776
              if (hideTimeout) {
4777
                $timeout.cancel(hideTimeout);
4778
                hideTimeout = null;
4779
              }
4780
4781
              if (transitionTimeout) {
4782
                $timeout.cancel(transitionTimeout);
4783
                transitionTimeout = null;
4784
              }
4785
            }
4786
4787
            function createTooltip() {
4788
              // There can only be one tooltip element per directive shown at once.
4789
              if (tooltip) {
4790
                return;
4791
              }
4792
4793
              tooltipLinkedScope = ttScope.$new();
4794
              tooltip = tooltipLinker(tooltipLinkedScope, function(tooltip) {
4795
                if (appendToBody) {
4796
                  $document.find('body').append(tooltip);
4797
                } else {
4798
                  element.after(tooltip);
4799
                }
4800
              });
4801
4802
              prepObservers();
4803
            }
4804
4805
            function removeTooltip() {
4806
              cancelShow();
4807
              cancelHide();
4808
              unregisterObservers();
4809
4810
              if (tooltip) {
4811
                tooltip.remove();
4812
                tooltip = null;
4813
              }
4814
              if (tooltipLinkedScope) {
4815
                tooltipLinkedScope.$destroy();
4816
                tooltipLinkedScope = null;
4817
              }
4818
            }
4819
4820
            /**
4821
             * Set the initial scope values. Once
4822
             * the tooltip is created, the observers
4823
             * will be added to keep things in sync.
4824
             */
4825
            function prepareTooltip() {
4826
              ttScope.title = attrs[prefix + 'Title'];
4827
              if (contentParse) {
4828
                ttScope.content = contentParse(scope);
4829
              } else {
4830
                ttScope.content = attrs[ttType];
4831
              }
4832
4833
              ttScope.popupClass = attrs[prefix + 'Class'];
4834
              ttScope.placement = angular.isDefined(attrs[prefix + 'Placement']) ? attrs[prefix + 'Placement'] : options.placement;
4835
              var placement = $position.parsePlacement(ttScope.placement);
4836
              lastPlacement = placement[1] ? placement[0] + '-' + placement[1] : placement[0];
4837
4838
              var delay = parseInt(attrs[prefix + 'PopupDelay'], 10);
4839
              var closeDelay = parseInt(attrs[prefix + 'PopupCloseDelay'], 10);
4840
              ttScope.popupDelay = !isNaN(delay) ? delay : options.popupDelay;
4841
              ttScope.popupCloseDelay = !isNaN(closeDelay) ? closeDelay : options.popupCloseDelay;
4842
            }
4843
4844
            function assignIsOpen(isOpen) {
4845
              if (isOpenParse && angular.isFunction(isOpenParse.assign)) {
4846
                isOpenParse.assign(scope, isOpen);
4847
              }
4848
            }
4849
4850
            ttScope.contentExp = function() {
4851
              return ttScope.content;
4852
            };
4853
4854
            /**
4855
             * Observe the relevant attributes.
4856
             */
4857
            attrs.$observe('disabled', function(val) {
4858
              if (val) {
4859
                cancelShow();
4860
              }
4861
4862
              if (val && ttScope.isOpen) {
4863
                hide();
4864
              }
4865
            });
4866
4867
            if (isOpenParse) {
4868
              scope.$watch(isOpenParse, function(val) {
4869
                if (ttScope && !val === ttScope.isOpen) {
4870
                  toggleTooltipBind();
4871
                }
4872
              });
4873
            }
4874
4875
            function prepObservers() {
4876
              observers.length = 0;
4877
4878
              if (contentParse) {
4879
                observers.push(
4880
                  scope.$watch(contentParse, function(val) {
4881
                    ttScope.content = val;
4882
                    if (!val && ttScope.isOpen) {
4883
                      hide();
4884
                    }
4885
                  })
4886
                );
4887
4888
                observers.push(
4889
                  tooltipLinkedScope.$watch(function() {
4890
                    if (!repositionScheduled) {
4891
                      repositionScheduled = true;
4892
                      tooltipLinkedScope.$$postDigest(function() {
4893
                        repositionScheduled = false;
4894
                        if (ttScope && ttScope.isOpen) {
4895
                          positionTooltip();
4896
                        }
4897
                      });
4898
                    }
4899
                  })
4900
                );
4901
              } else {
4902
                observers.push(
4903
                  attrs.$observe(ttType, function(val) {
4904
                    ttScope.content = val;
4905
                    if (!val && ttScope.isOpen) {
4906
                      hide();
4907
                    } else {
4908
                      positionTooltip();
4909
                    }
4910
                  })
4911
                );
4912
              }
4913
4914
              observers.push(
4915
                attrs.$observe(prefix + 'Title', function(val) {
4916
                  ttScope.title = val;
4917
                  if (ttScope.isOpen) {
4918
                    positionTooltip();
4919
                  }
4920
                })
4921
              );
4922
4923
              observers.push(
4924
                attrs.$observe(prefix + 'Placement', function(val) {
4925
                  ttScope.placement = val ? val : options.placement;
4926
                  if (ttScope.isOpen) {
4927
                    positionTooltip();
4928
                  }
4929
                })
4930
              );
4931
            }
4932
4933
            function unregisterObservers() {
4934
              if (observers.length) {
4935
                angular.forEach(observers, function(observer) {
4936
                  observer();
4937
                });
4938
                observers.length = 0;
4939
              }
4940
            }
4941
4942
            // hide tooltips/popovers for outsideClick trigger
4943
            function bodyHideTooltipBind(e) {
4944
              if (!ttScope || !ttScope.isOpen || !tooltip) {
4945
                return;
4946
              }
4947
              // make sure the tooltip/popover link or tool tooltip/popover itself were not clicked
4948
              if (!element[0].contains(e.target) && !tooltip[0].contains(e.target)) {
4949
                hideTooltipBind();
4950
              }
4951
            }
4952
4953
            var unregisterTriggers = function() {
4954
              triggers.show.forEach(function(trigger) {
4955
                if (trigger === 'outsideClick') {
4956
                  element.off('click', toggleTooltipBind);
4957
                } else {
4958
                  element.off(trigger, showTooltipBind);
4959
                  element.off(trigger, toggleTooltipBind);
4960
                }
4961
              });
4962
              triggers.hide.forEach(function(trigger) {
4963
                if (trigger === 'outsideClick') {
4964
                  $document.off('click', bodyHideTooltipBind);
4965
                } else {
4966
                  element.off(trigger, hideTooltipBind);
4967
                }
4968
              });
4969
            };
4970
4971
            function prepTriggers() {
4972
              var val = attrs[prefix + 'Trigger'];
4973
              unregisterTriggers();
4974
4975
              triggers = getTriggers(val);
4976
4977
              if (triggers.show !== 'none') {
4978
                triggers.show.forEach(function(trigger, idx) {
4979
                  if (trigger === 'outsideClick') {
4980
                    element.on('click', toggleTooltipBind);
4981
                    $document.on('click', bodyHideTooltipBind);
4982
                  } else if (trigger === triggers.hide[idx]) {
4983
                    element.on(trigger, toggleTooltipBind);
4984
                  } else if (trigger) {
4985
                    element.on(trigger, showTooltipBind);
4986
                    element.on(triggers.hide[idx], hideTooltipBind);
4987
                  }
4988
4989
                  element.on('keypress', function(e) {
4990
                    if (e.which === 27) {
4991
                      hideTooltipBind();
4992
                    }
4993
                  });
4994
                });
4995
              }
4996
            }
4997
4998
            prepTriggers();
4999
5000
            var animation = scope.$eval(attrs[prefix + 'Animation']);
5001
            ttScope.animation = angular.isDefined(animation) ? !!animation : options.animation;
5002
5003
            var appendToBodyVal;
5004
            var appendKey = prefix + 'AppendToBody';
5005
            if (appendKey in attrs && attrs[appendKey] === undefined) {
5006
              appendToBodyVal = true;
5007
            } else {
5008
              appendToBodyVal = scope.$eval(attrs[appendKey]);
5009
            }
5010
5011
            appendToBody = angular.isDefined(appendToBodyVal) ? appendToBodyVal : appendToBody;
5012
5013
            // Make sure tooltip is destroyed and removed.
5014
            scope.$on('$destroy', function onDestroyTooltip() {
5015
              unregisterTriggers();
5016
              removeTooltip();
5017
              openedTooltips.remove(ttScope);
5018
              ttScope = null;
5019
            });
5020
          };
5021
        }
5022
      };
5023
    };
5024
  }];
5025
})
5026
5027
// This is mostly ngInclude code but with a custom scope
5028
.directive('uibTooltipTemplateTransclude', [
5029
         '$animate', '$sce', '$compile', '$templateRequest',
5030 View Code Duplication
function ($animate, $sce, $compile, $templateRequest) {
5031
  return {
5032
    link: function(scope, elem, attrs) {
5033
      var origScope = scope.$eval(attrs.tooltipTemplateTranscludeScope);
5034
5035
      var changeCounter = 0,
5036
        currentScope,
5037
        previousElement,
5038
        currentElement;
5039
5040
      var cleanupLastIncludeContent = function() {
5041
        if (previousElement) {
5042
          previousElement.remove();
5043
          previousElement = null;
5044
        }
5045
5046
        if (currentScope) {
5047
          currentScope.$destroy();
5048
          currentScope = null;
5049
        }
5050
5051
        if (currentElement) {
5052
          $animate.leave(currentElement).then(function() {
5053
            previousElement = null;
5054
          });
5055
          previousElement = currentElement;
5056
          currentElement = null;
5057
        }
5058
      };
5059
5060
      scope.$watch($sce.parseAsResourceUrl(attrs.uibTooltipTemplateTransclude), function(src) {
5061
        var thisChangeId = ++changeCounter;
5062
5063
        if (src) {
5064
          //set the 2nd param to true to ignore the template request error so that the inner
5065
          //contents and scope can be cleaned up.
5066
          $templateRequest(src, true).then(function(response) {
5067
            if (thisChangeId !== changeCounter) { return; }
5068
            var newScope = origScope.$new();
5069
            var template = response;
5070
5071
            var clone = $compile(template)(newScope, function(clone) {
5072
              cleanupLastIncludeContent();
5073
              $animate.enter(clone, elem);
5074
            });
5075
5076
            currentScope = newScope;
5077
            currentElement = clone;
5078
5079
            currentScope.$emit('$includeContentLoaded', src);
5080
          }, function() {
5081
            if (thisChangeId === changeCounter) {
5082
              cleanupLastIncludeContent();
5083
              scope.$emit('$includeContentError', src);
5084
            }
5085
          });
5086
          scope.$emit('$includeContentRequested', src);
5087
        } else {
5088
          cleanupLastIncludeContent();
5089
        }
5090
      });
5091
5092
      scope.$on('$destroy', cleanupLastIncludeContent);
5093
    }
5094
  };
5095
}])
5096
5097
/**
5098
 * Note that it's intentional that these classes are *not* applied through $animate.
5099
 * They must not be animated as they're expected to be present on the tooltip on
5100
 * initialization.
5101
 */
5102
.directive('uibTooltipClasses', ['$uibPosition', function($uibPosition) {
5103
  return {
5104
    restrict: 'A',
5105
    link: function(scope, element, attrs) {
5106
      // need to set the primary position so the
5107
      // arrow has space during position measure.
5108
      // tooltip.positionTooltip()
5109
      if (scope.placement) {
5110
        // // There are no top-left etc... classes
5111
        // // in TWBS, so we need the primary position.
5112
        var position = $uibPosition.parsePlacement(scope.placement);
5113
        element.addClass(position[0]);
5114
      }
5115
5116
      if (scope.popupClass) {
5117
        element.addClass(scope.popupClass);
5118
      }
5119
5120
      if (scope.animation()) {
5121
        element.addClass(attrs.tooltipAnimationClass);
5122
      }
5123
    }
5124
  };
5125
}])
5126
5127
.directive('uibTooltipPopup', function() {
5128
  return {
5129
    replace: true,
5130
    scope: { content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5131
    templateUrl: 'uib/template/tooltip/tooltip-popup.html'
5132
  };
5133
})
5134
5135
.directive('uibTooltip', [ '$uibTooltip', function($uibTooltip) {
5136
  return $uibTooltip('uibTooltip', 'tooltip', 'mouseenter');
5137
}])
5138
5139
.directive('uibTooltipTemplatePopup', function() {
5140
  return {
5141
    replace: true,
5142
    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5143
      originScope: '&' },
5144
    templateUrl: 'uib/template/tooltip/tooltip-template-popup.html'
5145
  };
5146
})
5147
5148
.directive('uibTooltipTemplate', ['$uibTooltip', function($uibTooltip) {
5149
  return $uibTooltip('uibTooltipTemplate', 'tooltip', 'mouseenter', {
5150
    useContentExp: true
5151
  });
5152
}])
5153
5154
.directive('uibTooltipHtmlPopup', function() {
5155
  return {
5156
    replace: true,
5157
    scope: { contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5158
    templateUrl: 'uib/template/tooltip/tooltip-html-popup.html'
5159
  };
5160
})
5161
5162
.directive('uibTooltipHtml', ['$uibTooltip', function($uibTooltip) {
5163
  return $uibTooltip('uibTooltipHtml', 'tooltip', 'mouseenter', {
5164
    useContentExp: true
5165
  });
5166
}]);
5167
5168
/**
5169
 * The following features are still outstanding: popup delay, animation as a
5170
 * function, placement as a function, inside, support for more triggers than
5171
 * just mouse enter/leave, and selector delegatation.
5172
 */
5173
angular.module('ui.bootstrap.popover', ['ui.bootstrap.tooltip'])
5174
5175
.directive('uibPopoverTemplatePopup', function() {
5176
  return {
5177
    replace: true,
5178
    scope: { uibTitle: '@', contentExp: '&', placement: '@', popupClass: '@', animation: '&', isOpen: '&',
5179
      originScope: '&' },
5180
    templateUrl: 'uib/template/popover/popover-template.html'
5181
  };
5182
})
5183
5184
.directive('uibPopoverTemplate', ['$uibTooltip', function($uibTooltip) {
5185
  return $uibTooltip('uibPopoverTemplate', 'popover', 'click', {
5186
    useContentExp: true
5187
  });
5188
}])
5189
5190
.directive('uibPopoverHtmlPopup', function() {
5191
  return {
5192
    replace: true,
5193
    scope: { contentExp: '&', uibTitle: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5194
    templateUrl: 'uib/template/popover/popover-html.html'
5195
  };
5196
})
5197
5198
.directive('uibPopoverHtml', ['$uibTooltip', function($uibTooltip) {
5199
  return $uibTooltip('uibPopoverHtml', 'popover', 'click', {
5200
    useContentExp: true
5201
  });
5202
}])
5203
5204
.directive('uibPopoverPopup', function() {
5205
  return {
5206
    replace: true,
5207
    scope: { uibTitle: '@', content: '@', placement: '@', popupClass: '@', animation: '&', isOpen: '&' },
5208
    templateUrl: 'uib/template/popover/popover.html'
5209
  };
5210
})
5211
5212
.directive('uibPopover', ['$uibTooltip', function($uibTooltip) {
5213
  return $uibTooltip('uibPopover', 'popover', 'click');
5214
}]);
5215
5216
angular.module('ui.bootstrap.progressbar', [])
5217
5218
.constant('uibProgressConfig', {
5219
  animate: true,
5220
  max: 100
5221
})
5222
5223 View Code Duplication
.controller('UibProgressController', ['$scope', '$attrs', 'uibProgressConfig', function($scope, $attrs, progressConfig) {
5224
  var self = this,
5225
      animate = angular.isDefined($attrs.animate) ? $scope.$parent.$eval($attrs.animate) : progressConfig.animate;
5226
5227
  this.bars = [];
5228
  $scope.max = getMaxOrDefault();
5229
5230
  this.addBar = function(bar, element, attrs) {
5231
    if (!animate) {
5232
      element.css({'transition': 'none'});
5233
    }
5234
5235
    this.bars.push(bar);
5236
5237
    bar.max = getMaxOrDefault();
5238
    bar.title = attrs && angular.isDefined(attrs.title) ? attrs.title : 'progressbar';
5239
5240
    bar.$watch('value', function(value) {
5241
      bar.recalculatePercentage();
5242
    });
5243
5244
    bar.recalculatePercentage = function() {
5245
      var totalPercentage = self.bars.reduce(function(total, bar) {
5246
        bar.percent = +(100 * bar.value / bar.max).toFixed(2);
5247
        return total + bar.percent;
5248
      }, 0);
5249
5250
      if (totalPercentage > 100) {
5251
        bar.percent -= totalPercentage - 100;
5252
      }
5253
    };
5254
5255
    bar.$on('$destroy', function() {
5256
      element = null;
5257
      self.removeBar(bar);
5258
    });
5259
  };
5260
5261
  this.removeBar = function(bar) {
5262
    this.bars.splice(this.bars.indexOf(bar), 1);
5263
    this.bars.forEach(function (bar) {
5264
      bar.recalculatePercentage();
5265
    });
5266
  };
5267
5268
  //$attrs.$observe('maxParam', function(maxParam) {
5269
  $scope.$watch('maxParam', function(maxParam) {
5270
    self.bars.forEach(function(bar) {
5271
      bar.max = getMaxOrDefault();
5272
      bar.recalculatePercentage();
5273
    });
5274
  });
5275
5276
  function getMaxOrDefault () {
5277
    return angular.isDefined($scope.maxParam) ? $scope.maxParam : progressConfig.max;
5278
  }
5279
}])
5280
5281
.directive('uibProgress', function() {
5282
  return {
5283
    replace: true,
5284
    transclude: true,
5285
    controller: 'UibProgressController',
5286
    require: 'uibProgress',
5287
    scope: {
5288
      maxParam: '=?max'
5289
    },
5290
    templateUrl: 'uib/template/progressbar/progress.html'
5291
  };
5292
})
5293
5294
.directive('uibBar', function() {
5295
  return {
5296
    replace: true,
5297
    transclude: true,
5298
    require: '^uibProgress',
5299
    scope: {
5300
      value: '=',
5301
      type: '@'
5302
    },
5303
    templateUrl: 'uib/template/progressbar/bar.html',
5304
    link: function(scope, element, attrs, progressCtrl) {
5305
      progressCtrl.addBar(scope, element, attrs);
5306
    }
5307
  };
5308
})
5309
5310
.directive('uibProgressbar', function() {
5311
  return {
5312
    replace: true,
5313
    transclude: true,
5314
    controller: 'UibProgressController',
5315
    scope: {
5316
      value: '=',
5317
      maxParam: '=?max',
5318
      type: '@'
5319
    },
5320
    templateUrl: 'uib/template/progressbar/progressbar.html',
5321
    link: function(scope, element, attrs, progressCtrl) {
5322
      progressCtrl.addBar(scope, angular.element(element.children()[0]), {title: attrs.title});
5323
    }
5324
  };
5325
});
5326
5327
angular.module('ui.bootstrap.rating', [])
5328
5329
.constant('uibRatingConfig', {
5330
  max: 5,
5331
  stateOn: null,
5332
  stateOff: null,
5333
  enableReset: true,
5334
  titles : ['one', 'two', 'three', 'four', 'five']
5335
})
5336
5337 View Code Duplication
.controller('UibRatingController', ['$scope', '$attrs', 'uibRatingConfig', function($scope, $attrs, ratingConfig) {
5338
  var ngModelCtrl = { $setViewValue: angular.noop },
5339
    self = this;
5340
5341
  this.init = function(ngModelCtrl_) {
5342
    ngModelCtrl = ngModelCtrl_;
5343
    ngModelCtrl.$render = this.render;
5344
5345
    ngModelCtrl.$formatters.push(function(value) {
5346
      if (angular.isNumber(value) && value << 0 !== value) {
5347
        value = Math.round(value);
5348
      }
5349
5350
      return value;
5351
    });
5352
5353
    this.stateOn = angular.isDefined($attrs.stateOn) ? $scope.$parent.$eval($attrs.stateOn) : ratingConfig.stateOn;
5354
    this.stateOff = angular.isDefined($attrs.stateOff) ? $scope.$parent.$eval($attrs.stateOff) : ratingConfig.stateOff;
5355
    this.enableReset = angular.isDefined($attrs.enableReset) ?
5356
      $scope.$parent.$eval($attrs.enableReset) : ratingConfig.enableReset;
5357
    var tmpTitles = angular.isDefined($attrs.titles) ? $scope.$parent.$eval($attrs.titles) : ratingConfig.titles;
5358
    this.titles = angular.isArray(tmpTitles) && tmpTitles.length > 0 ?
5359
      tmpTitles : ratingConfig.titles;
5360
5361
    var ratingStates = angular.isDefined($attrs.ratingStates) ?
5362
      $scope.$parent.$eval($attrs.ratingStates) :
5363
      new Array(angular.isDefined($attrs.max) ? $scope.$parent.$eval($attrs.max) : ratingConfig.max);
0 ignored issues
show
Coding Style Best Practice introduced by
Using the Array constructor is generally discouraged. Consider using an array literal instead.
Loading history...
5364
    $scope.range = this.buildTemplateObjects(ratingStates);
5365
  };
5366
5367
  this.buildTemplateObjects = function(states) {
5368
    for (var i = 0, n = states.length; i < n; i++) {
5369
      states[i] = angular.extend({ index: i }, { stateOn: this.stateOn, stateOff: this.stateOff, title: this.getTitle(i) }, states[i]);
5370
    }
5371
    return states;
5372
  };
5373
5374
  this.getTitle = function(index) {
5375
    if (index >= this.titles.length) {
5376
      return index + 1;
5377
    }
5378
5379
    return this.titles[index];
5380
  };
5381
5382
  $scope.rate = function(value) {
5383
    if (!$scope.readonly && value >= 0 && value <= $scope.range.length) {
5384
      var newViewValue = self.enableReset && ngModelCtrl.$viewValue === value ? 0 : value;
5385
      ngModelCtrl.$setViewValue(newViewValue);
5386
      ngModelCtrl.$render();
5387
    }
5388
  };
5389
5390
  $scope.enter = function(value) {
5391
    if (!$scope.readonly) {
5392
      $scope.value = value;
5393
    }
5394
    $scope.onHover({value: value});
5395
  };
5396
5397
  $scope.reset = function() {
5398
    $scope.value = ngModelCtrl.$viewValue;
5399
    $scope.onLeave();
5400
  };
5401
5402
  $scope.onKeydown = function(evt) {
5403
    if (/(37|38|39|40)/.test(evt.which)) {
5404
      evt.preventDefault();
5405
      evt.stopPropagation();
5406
      $scope.rate($scope.value + (evt.which === 38 || evt.which === 39 ? 1 : -1));
5407
    }
5408
  };
5409
5410
  this.render = function() {
5411
    $scope.value = ngModelCtrl.$viewValue;
5412
    $scope.title = self.getTitle($scope.value - 1);
5413
  };
5414
}])
5415
5416
.directive('uibRating', function() {
5417
  return {
5418
    require: ['uibRating', 'ngModel'],
5419
    scope: {
5420
      readonly: '=?readOnly',
5421
      onHover: '&',
5422
      onLeave: '&'
5423
    },
5424
    controller: 'UibRatingController',
5425
    templateUrl: 'uib/template/rating/rating.html',
5426
    replace: true,
5427
    link: function(scope, element, attrs, ctrls) {
5428
      var ratingCtrl = ctrls[0], ngModelCtrl = ctrls[1];
5429
      ratingCtrl.init(ngModelCtrl);
5430
    }
5431
  };
5432
});
5433
5434
angular.module('ui.bootstrap.tabs', [])
5435
5436 View Code Duplication
.controller('UibTabsetController', ['$scope', function ($scope) {
5437
  var ctrl = this,
5438
    oldIndex;
5439
  ctrl.tabs = [];
5440
5441
  ctrl.select = function(index, evt) {
5442
    if (!destroyed) {
5443
      var previousIndex = findTabIndex(oldIndex);
5444
      var previousSelected = ctrl.tabs[previousIndex];
5445
      if (previousSelected) {
5446
        previousSelected.tab.onDeselect({
5447
          $event: evt
5448
        });
5449
        if (evt && evt.isDefaultPrevented()) {
5450
          return;
5451
        }
5452
        previousSelected.tab.active = false;
5453
      }
5454
5455
      var selected = ctrl.tabs[index];
5456
      if (selected) {
5457
        selected.tab.onSelect({
5458
          $event: evt
5459
        });
5460
        selected.tab.active = true;
5461
        ctrl.active = selected.index;
5462
        oldIndex = selected.index;
5463
      } else if (!selected && angular.isNumber(oldIndex)) {
5464
        ctrl.active = null;
5465
        oldIndex = null;
5466
      }
5467
    }
5468
  };
5469
5470
  ctrl.addTab = function addTab(tab) {
5471
    ctrl.tabs.push({
5472
      tab: tab,
5473
      index: tab.index
5474
    });
5475
    ctrl.tabs.sort(function(t1, t2) {
5476
      if (t1.index > t2.index) {
5477
        return 1;
5478
      }
5479
5480
      if (t1.index < t2.index) {
5481
        return -1;
5482
      }
5483
5484
      return 0;
5485
    });
5486
5487
    if (tab.index === ctrl.active || !angular.isNumber(ctrl.active) && ctrl.tabs.length === 1) {
5488
      var newActiveIndex = findTabIndex(tab.index);
5489
      ctrl.select(newActiveIndex);
5490
    }
5491
  };
5492
5493
  ctrl.removeTab = function removeTab(tab) {
5494
    var index;
5495
    for (var i = 0; i < ctrl.tabs.length; i++) {
5496
      if (ctrl.tabs[i].tab === tab) {
5497
        index = i;
5498
        break;
5499
      }
5500
    }
5501
5502
    if (ctrl.tabs[index].index === ctrl.active) {
5503
      var newActiveTabIndex = index === ctrl.tabs.length - 1 ?
5504
        index - 1 : index + 1 % ctrl.tabs.length;
5505
      ctrl.select(newActiveTabIndex);
5506
    }
5507
5508
    ctrl.tabs.splice(index, 1);
5509
  };
5510
5511
  $scope.$watch('tabset.active', function(val) {
5512
    if (angular.isNumber(val) && val !== oldIndex) {
5513
      ctrl.select(findTabIndex(val));
5514
    }
5515
  });
5516
5517
  var destroyed;
5518
  $scope.$on('$destroy', function() {
5519
    destroyed = true;
5520
  });
5521
5522
  function findTabIndex(index) {
5523
    for (var i = 0; i < ctrl.tabs.length; i++) {
5524
      if (ctrl.tabs[i].index === index) {
5525
        return i;
5526
      }
5527
    }
5528
  }
5529
}])
5530
5531 View Code Duplication
.directive('uibTabset', function() {
5532
  return {
5533
    transclude: true,
5534
    replace: true,
5535
    scope: {},
5536
    bindToController: {
5537
      active: '=?',
5538
      type: '@'
5539
    },
5540
    controller: 'UibTabsetController',
5541
    controllerAs: 'tabset',
5542
    templateUrl: function(element, attrs) {
5543
      return attrs.templateUrl || 'uib/template/tabs/tabset.html';
5544
    },
5545
    link: function(scope, element, attrs) {
5546
      scope.vertical = angular.isDefined(attrs.vertical) ?
5547
        scope.$parent.$eval(attrs.vertical) : false;
5548
      scope.justified = angular.isDefined(attrs.justified) ?
5549
        scope.$parent.$eval(attrs.justified) : false;
5550
      if (angular.isUndefined(attrs.active)) {
5551
        scope.active = 0;
5552
      }
5553
    }
5554
  };
5555
})
5556
5557 View Code Duplication
.directive('uibTab', ['$parse', function($parse) {
5558
  return {
5559
    require: '^uibTabset',
5560
    replace: true,
5561
    templateUrl: function(element, attrs) {
5562
      return attrs.templateUrl || 'uib/template/tabs/tab.html';
5563
    },
5564
    transclude: true,
5565
    scope: {
5566
      heading: '@',
5567
      index: '=?',
5568
      classes: '@?',
5569
      onSelect: '&select', //This callback is called in contentHeadingTransclude
5570
                          //once it inserts the tab's content into the dom
5571
      onDeselect: '&deselect'
5572
    },
5573
    controller: function() {
5574
      //Empty controller so other directives can require being 'under' a tab
5575
    },
5576
    controllerAs: 'tab',
5577
    link: function(scope, elm, attrs, tabsetCtrl, transclude) {
5578
      scope.disabled = false;
5579
      if (attrs.disable) {
5580
        scope.$parent.$watch($parse(attrs.disable), function(value) {
5581
          scope.disabled = !! value;
5582
        });
5583
      }
5584
5585
      if (angular.isUndefined(attrs.index)) {
5586
        if (tabsetCtrl.tabs && tabsetCtrl.tabs.length) {
5587
          scope.index = Math.max.apply(null, tabsetCtrl.tabs.map(function(t) { return t.index; })) + 1;
5588
        } else {
5589
          scope.index = 0;
5590
        }
5591
      }
5592
5593
      if (angular.isUndefined(attrs.classes)) {
5594
        scope.classes = '';
5595
      }
5596
5597
      scope.select = function(evt) {
5598
        if (!scope.disabled) {
5599
          var index;
5600
          for (var i = 0; i < tabsetCtrl.tabs.length; i++) {
5601
            if (tabsetCtrl.tabs[i].tab === scope) {
5602
              index = i;
5603
              break;
5604
            }
5605
          }
5606
5607
          tabsetCtrl.select(index, evt);
0 ignored issues
show
Bug introduced by
The variable index seems to not be initialized for all possible execution paths. Are you sure select handles undefined variables?
Loading history...
5608
        }
5609
      };
5610
5611
      tabsetCtrl.addTab(scope);
5612
      scope.$on('$destroy', function() {
5613
        tabsetCtrl.removeTab(scope);
5614
      });
5615
5616
      //We need to transclude later, once the content container is ready.
5617
      //when this link happens, we're inside a tab heading.
5618
      scope.$transcludeFn = transclude;
5619
    }
5620
  };
5621
}])
5622
5623
.directive('uibTabHeadingTransclude', function() {
5624
  return {
5625
    restrict: 'A',
5626
    require: '^uibTab',
5627
    link: function(scope, elm) {
5628
      scope.$watch('headingElement', function updateHeadingElement(heading) {
5629
        if (heading) {
5630
          elm.html('');
5631
          elm.append(heading);
5632
        }
5633
      });
5634
    }
5635
  };
5636
})
5637
5638 View Code Duplication
.directive('uibTabContentTransclude', function() {
5639
  return {
5640
    restrict: 'A',
5641
    require: '^uibTabset',
5642
    link: function(scope, elm, attrs) {
5643
      var tab = scope.$eval(attrs.uibTabContentTransclude).tab;
5644
5645
      //Now our tab is ready to be transcluded: both the tab heading area
5646
      //and the tab content area are loaded.  Transclude 'em both.
5647
      tab.$transcludeFn(tab.$parent, function(contents) {
5648
        angular.forEach(contents, function(node) {
5649
          if (isTabHeading(node)) {
5650
            //Let tabHeadingTransclude know.
5651
            tab.headingElement = node;
5652
          } else {
5653
            elm.append(node);
5654
          }
5655
        });
5656
      });
5657
    }
5658
  };
5659
5660
  function isTabHeading(node) {
5661
    return node.tagName && (
5662
      node.hasAttribute('uib-tab-heading') ||
5663
      node.hasAttribute('data-uib-tab-heading') ||
5664
      node.hasAttribute('x-uib-tab-heading') ||
5665
      node.tagName.toLowerCase() === 'uib-tab-heading' ||
5666
      node.tagName.toLowerCase() === 'data-uib-tab-heading' ||
5667
      node.tagName.toLowerCase() === 'x-uib-tab-heading' ||
5668
      node.tagName.toLowerCase() === 'uib:tab-heading'
5669
    );
5670
  }
5671
});
5672
5673
angular.module('ui.bootstrap.timepicker', [])
5674
5675
.constant('uibTimepickerConfig', {
5676
  hourStep: 1,
5677
  minuteStep: 1,
5678
  secondStep: 1,
5679
  showMeridian: true,
5680
  showSeconds: false,
5681
  meridians: null,
5682
  readonlyInput: false,
5683
  mousewheel: true,
5684
  arrowkeys: true,
5685
  showSpinners: true,
5686
  templateUrl: 'uib/template/timepicker/timepicker.html'
5687
})
5688
5689 View Code Duplication
.controller('UibTimepickerController', ['$scope', '$element', '$attrs', '$parse', '$log', '$locale', 'uibTimepickerConfig', function($scope, $element, $attrs, $parse, $log, $locale, timepickerConfig) {
5690
  var selected = new Date(),
5691
    watchers = [],
5692
    ngModelCtrl = { $setViewValue: angular.noop }, // nullModelCtrl
5693
    meridians = angular.isDefined($attrs.meridians) ? $scope.$parent.$eval($attrs.meridians) : timepickerConfig.meridians || $locale.DATETIME_FORMATS.AMPMS,
5694
    padHours = angular.isDefined($attrs.padHours) ? $scope.$parent.$eval($attrs.padHours) : true;
5695
5696
  $scope.tabindex = angular.isDefined($attrs.tabindex) ? $attrs.tabindex : 0;
5697
  $element.removeAttr('tabindex');
5698
5699
  this.init = function(ngModelCtrl_, inputs) {
5700
    ngModelCtrl = ngModelCtrl_;
5701
    ngModelCtrl.$render = this.render;
5702
5703
    ngModelCtrl.$formatters.unshift(function(modelValue) {
5704
      return modelValue ? new Date(modelValue) : null;
5705
    });
5706
5707
    var hoursInputEl = inputs.eq(0),
5708
        minutesInputEl = inputs.eq(1),
5709
        secondsInputEl = inputs.eq(2);
5710
5711
    var mousewheel = angular.isDefined($attrs.mousewheel) ? $scope.$parent.$eval($attrs.mousewheel) : timepickerConfig.mousewheel;
5712
5713
    if (mousewheel) {
5714
      this.setupMousewheelEvents(hoursInputEl, minutesInputEl, secondsInputEl);
5715
    }
5716
5717
    var arrowkeys = angular.isDefined($attrs.arrowkeys) ? $scope.$parent.$eval($attrs.arrowkeys) : timepickerConfig.arrowkeys;
5718
    if (arrowkeys) {
5719
      this.setupArrowkeyEvents(hoursInputEl, minutesInputEl, secondsInputEl);
5720
    }
5721
5722
    $scope.readonlyInput = angular.isDefined($attrs.readonlyInput) ? $scope.$parent.$eval($attrs.readonlyInput) : timepickerConfig.readonlyInput;
5723
    this.setupInputEvents(hoursInputEl, minutesInputEl, secondsInputEl);
5724
  };
5725
5726
  var hourStep = timepickerConfig.hourStep;
5727
  if ($attrs.hourStep) {
5728
    watchers.push($scope.$parent.$watch($parse($attrs.hourStep), function(value) {
5729
      hourStep = +value;
5730
    }));
5731
  }
5732
5733
  var minuteStep = timepickerConfig.minuteStep;
5734
  if ($attrs.minuteStep) {
5735
    watchers.push($scope.$parent.$watch($parse($attrs.minuteStep), function(value) {
5736
      minuteStep = +value;
5737
    }));
5738
  }
5739
5740
  var min;
5741
  watchers.push($scope.$parent.$watch($parse($attrs.min), function(value) {
5742
    var dt = new Date(value);
5743
    min = isNaN(dt) ? undefined : dt;
5744
  }));
5745
5746
  var max;
5747
  watchers.push($scope.$parent.$watch($parse($attrs.max), function(value) {
5748
    var dt = new Date(value);
5749
    max = isNaN(dt) ? undefined : dt;
5750
  }));
5751
5752
  var disabled = false;
5753
  if ($attrs.ngDisabled) {
5754
    watchers.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(value) {
5755
      disabled = value;
5756
    }));
5757
  }
5758
5759
  $scope.noIncrementHours = function() {
5760
    var incrementedSelected = addMinutes(selected, hourStep * 60);
5761
    return disabled || incrementedSelected > max ||
5762
      incrementedSelected < selected && incrementedSelected < min;
5763
  };
5764
5765
  $scope.noDecrementHours = function() {
5766
    var decrementedSelected = addMinutes(selected, -hourStep * 60);
5767
    return disabled || decrementedSelected < min ||
5768
      decrementedSelected > selected && decrementedSelected > max;
5769
  };
5770
5771
  $scope.noIncrementMinutes = function() {
5772
    var incrementedSelected = addMinutes(selected, minuteStep);
5773
    return disabled || incrementedSelected > max ||
5774
      incrementedSelected < selected && incrementedSelected < min;
5775
  };
5776
5777
  $scope.noDecrementMinutes = function() {
5778
    var decrementedSelected = addMinutes(selected, -minuteStep);
5779
    return disabled || decrementedSelected < min ||
5780
      decrementedSelected > selected && decrementedSelected > max;
5781
  };
5782
5783
  $scope.noIncrementSeconds = function() {
5784
    var incrementedSelected = addSeconds(selected, secondStep);
5785
    return disabled || incrementedSelected > max ||
5786
      incrementedSelected < selected && incrementedSelected < min;
5787
  };
5788
5789
  $scope.noDecrementSeconds = function() {
5790
    var decrementedSelected = addSeconds(selected, -secondStep);
5791
    return disabled || decrementedSelected < min ||
5792
      decrementedSelected > selected && decrementedSelected > max;
5793
  };
5794
5795
  $scope.noToggleMeridian = function() {
5796
    if (selected.getHours() < 12) {
5797
      return disabled || addMinutes(selected, 12 * 60) > max;
5798
    }
5799
5800
    return disabled || addMinutes(selected, -12 * 60) < min;
5801
  };
5802
5803
  var secondStep = timepickerConfig.secondStep;
5804
  if ($attrs.secondStep) {
5805
    watchers.push($scope.$parent.$watch($parse($attrs.secondStep), function(value) {
5806
      secondStep = +value;
5807
    }));
5808
  }
5809
5810
  $scope.showSeconds = timepickerConfig.showSeconds;
5811
  if ($attrs.showSeconds) {
5812
    watchers.push($scope.$parent.$watch($parse($attrs.showSeconds), function(value) {
5813
      $scope.showSeconds = !!value;
5814
    }));
5815
  }
5816
5817
  // 12H / 24H mode
5818
  $scope.showMeridian = timepickerConfig.showMeridian;
5819
  if ($attrs.showMeridian) {
5820
    watchers.push($scope.$parent.$watch($parse($attrs.showMeridian), function(value) {
5821
      $scope.showMeridian = !!value;
5822
5823
      if (ngModelCtrl.$error.time) {
5824
        // Evaluate from template
5825
        var hours = getHoursFromTemplate(), minutes = getMinutesFromTemplate();
5826
        if (angular.isDefined(hours) && angular.isDefined(minutes)) {
5827
          selected.setHours(hours);
5828
          refresh();
5829
        }
5830
      } else {
5831
        updateTemplate();
5832
      }
5833
    }));
5834
  }
5835
5836
  // Get $scope.hours in 24H mode if valid
5837
  function getHoursFromTemplate() {
5838
    var hours = +$scope.hours;
5839
    var valid = $scope.showMeridian ? hours > 0 && hours < 13 :
5840
      hours >= 0 && hours < 24;
5841
    if (!valid || $scope.hours === '') {
5842
      return undefined;
5843
    }
5844
5845
    if ($scope.showMeridian) {
5846
      if (hours === 12) {
5847
        hours = 0;
5848
      }
5849
      if ($scope.meridian === meridians[1]) {
5850
        hours = hours + 12;
5851
      }
5852
    }
5853
    return hours;
5854
  }
5855
5856
  function getMinutesFromTemplate() {
5857
    var minutes = +$scope.minutes;
5858
    var valid = minutes >= 0 && minutes < 60;
5859
    if (!valid || $scope.minutes === '') {
5860
      return undefined;
5861
    }
5862
    return minutes;
5863
  }
5864
5865
  function getSecondsFromTemplate() {
5866
    var seconds = +$scope.seconds;
5867
    return seconds >= 0 && seconds < 60 ? seconds : undefined;
5868
  }
5869
5870
  function pad(value, noPad) {
5871
    if (value === null) {
5872
      return '';
5873
    }
5874
5875
    return angular.isDefined(value) && value.toString().length < 2 && !noPad ?
5876
      '0' + value : value.toString();
5877
  }
5878
5879
  // Respond on mousewheel spin
5880
  this.setupMousewheelEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
5881
    var isScrollingUp = function(e) {
5882
      if (e.originalEvent) {
5883
        e = e.originalEvent;
5884
      }
5885
      //pick correct delta variable depending on event
5886
      var delta = e.wheelDelta ? e.wheelDelta : -e.deltaY;
5887
      return e.detail || delta > 0;
5888
    };
5889
5890
    hoursInputEl.bind('mousewheel wheel', function(e) {
5891
      if (!disabled) {
5892
        $scope.$apply(isScrollingUp(e) ? $scope.incrementHours() : $scope.decrementHours());
5893
      }
5894
      e.preventDefault();
5895
    });
5896
5897
    minutesInputEl.bind('mousewheel wheel', function(e) {
5898
      if (!disabled) {
5899
        $scope.$apply(isScrollingUp(e) ? $scope.incrementMinutes() : $scope.decrementMinutes());
5900
      }
5901
      e.preventDefault();
5902
    });
5903
5904
     secondsInputEl.bind('mousewheel wheel', function(e) {
5905
      if (!disabled) {
5906
        $scope.$apply(isScrollingUp(e) ? $scope.incrementSeconds() : $scope.decrementSeconds());
5907
      }
5908
      e.preventDefault();
5909
    });
5910
  };
5911
5912
  // Respond on up/down arrowkeys
5913
  this.setupArrowkeyEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
5914
    hoursInputEl.bind('keydown', function(e) {
5915
      if (!disabled) {
5916
        if (e.which === 38) { // up
5917
          e.preventDefault();
5918
          $scope.incrementHours();
5919
          $scope.$apply();
5920
        } else if (e.which === 40) { // down
5921
          e.preventDefault();
5922
          $scope.decrementHours();
5923
          $scope.$apply();
5924
        }
5925
      }
5926
    });
5927
5928
    minutesInputEl.bind('keydown', function(e) {
5929
      if (!disabled) {
5930
        if (e.which === 38) { // up
5931
          e.preventDefault();
5932
          $scope.incrementMinutes();
5933
          $scope.$apply();
5934
        } else if (e.which === 40) { // down
5935
          e.preventDefault();
5936
          $scope.decrementMinutes();
5937
          $scope.$apply();
5938
        }
5939
      }
5940
    });
5941
5942
    secondsInputEl.bind('keydown', function(e) {
5943
      if (!disabled) {
5944
        if (e.which === 38) { // up
5945
          e.preventDefault();
5946
          $scope.incrementSeconds();
5947
          $scope.$apply();
5948
        } else if (e.which === 40) { // down
5949
          e.preventDefault();
5950
          $scope.decrementSeconds();
5951
          $scope.$apply();
5952
        }
5953
      }
5954
    });
5955
  };
5956
5957
  this.setupInputEvents = function(hoursInputEl, minutesInputEl, secondsInputEl) {
5958
    if ($scope.readonlyInput) {
5959
      $scope.updateHours = angular.noop;
5960
      $scope.updateMinutes = angular.noop;
5961
      $scope.updateSeconds = angular.noop;
5962
      return;
5963
    }
5964
5965
    var invalidate = function(invalidHours, invalidMinutes, invalidSeconds) {
5966
      ngModelCtrl.$setViewValue(null);
5967
      ngModelCtrl.$setValidity('time', false);
5968
      if (angular.isDefined(invalidHours)) {
5969
        $scope.invalidHours = invalidHours;
5970
      }
5971
5972
      if (angular.isDefined(invalidMinutes)) {
5973
        $scope.invalidMinutes = invalidMinutes;
5974
      }
5975
5976
      if (angular.isDefined(invalidSeconds)) {
5977
        $scope.invalidSeconds = invalidSeconds;
5978
      }
5979
    };
5980
5981
    $scope.updateHours = function() {
5982
      var hours = getHoursFromTemplate(),
5983
        minutes = getMinutesFromTemplate();
5984
5985
      ngModelCtrl.$setDirty();
5986
5987
      if (angular.isDefined(hours) && angular.isDefined(minutes)) {
5988
        selected.setHours(hours);
5989
        selected.setMinutes(minutes);
5990
        if (selected < min || selected > max) {
5991
          invalidate(true);
5992
        } else {
5993
          refresh('h');
5994
        }
5995
      } else {
5996
        invalidate(true);
5997
      }
5998
    };
5999
6000
    hoursInputEl.bind('blur', function(e) {
6001
      ngModelCtrl.$setTouched();
6002
      if (modelIsEmpty()) {
6003
        makeValid();
6004
      } else if ($scope.hours === null || $scope.hours === '') {
6005
        invalidate(true);
6006
      } else if (!$scope.invalidHours && $scope.hours < 10) {
6007
        $scope.$apply(function() {
6008
          $scope.hours = pad($scope.hours, !padHours);
6009
        });
6010
      }
6011
    });
6012
6013
    $scope.updateMinutes = function() {
6014
      var minutes = getMinutesFromTemplate(),
6015
        hours = getHoursFromTemplate();
6016
6017
      ngModelCtrl.$setDirty();
6018
6019
      if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6020
        selected.setHours(hours);
6021
        selected.setMinutes(minutes);
6022
        if (selected < min || selected > max) {
6023
          invalidate(undefined, true);
6024
        } else {
6025
          refresh('m');
6026
        }
6027
      } else {
6028
        invalidate(undefined, true);
6029
      }
6030
    };
6031
6032
    minutesInputEl.bind('blur', function(e) {
6033
      ngModelCtrl.$setTouched();
6034
      if (modelIsEmpty()) {
6035
        makeValid();
6036
      } else if ($scope.minutes === null) {
6037
        invalidate(undefined, true);
6038
      } else if (!$scope.invalidMinutes && $scope.minutes < 10) {
6039
        $scope.$apply(function() {
6040
          $scope.minutes = pad($scope.minutes);
6041
        });
6042
      }
6043
    });
6044
6045
    $scope.updateSeconds = function() {
6046
      var seconds = getSecondsFromTemplate();
6047
6048
      ngModelCtrl.$setDirty();
6049
6050
      if (angular.isDefined(seconds)) {
6051
        selected.setSeconds(seconds);
6052
        refresh('s');
6053
      } else {
6054
        invalidate(undefined, undefined, true);
6055
      }
6056
    };
6057
6058
    secondsInputEl.bind('blur', function(e) {
6059
      if (modelIsEmpty()) {
6060
        makeValid();
6061
      } else if (!$scope.invalidSeconds && $scope.seconds < 10) {
6062
        $scope.$apply( function() {
6063
          $scope.seconds = pad($scope.seconds);
6064
        });
6065
      }
6066
    });
6067
6068
  };
6069
6070
  this.render = function() {
6071
    var date = ngModelCtrl.$viewValue;
6072
6073
    if (isNaN(date)) {
6074
      ngModelCtrl.$setValidity('time', false);
6075
      $log.error('Timepicker 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.');
6076
    } else {
6077
      if (date) {
6078
        selected = date;
6079
      }
6080
6081
      if (selected < min || selected > max) {
6082
        ngModelCtrl.$setValidity('time', false);
6083
        $scope.invalidHours = true;
6084
        $scope.invalidMinutes = true;
6085
      } else {
6086
        makeValid();
6087
      }
6088
      updateTemplate();
6089
    }
6090
  };
6091
6092
  // Call internally when we know that model is valid.
6093
  function refresh(keyboardChange) {
6094
    makeValid();
6095
    ngModelCtrl.$setViewValue(new Date(selected));
6096
    updateTemplate(keyboardChange);
6097
  }
6098
6099
  function makeValid() {
6100
    ngModelCtrl.$setValidity('time', true);
6101
    $scope.invalidHours = false;
6102
    $scope.invalidMinutes = false;
6103
    $scope.invalidSeconds = false;
6104
  }
6105
6106
  function updateTemplate(keyboardChange) {
6107
    if (!ngModelCtrl.$modelValue) {
6108
      $scope.hours = null;
6109
      $scope.minutes = null;
6110
      $scope.seconds = null;
6111
      $scope.meridian = meridians[0];
6112
    } else {
6113
      var hours = selected.getHours(),
6114
        minutes = selected.getMinutes(),
6115
        seconds = selected.getSeconds();
6116
6117
      if ($scope.showMeridian) {
6118
        hours = hours === 0 || hours === 12 ? 12 : hours % 12; // Convert 24 to 12 hour system
6119
      }
6120
6121
      $scope.hours = keyboardChange === 'h' ? hours : pad(hours, !padHours);
6122
      if (keyboardChange !== 'm') {
6123
        $scope.minutes = pad(minutes);
6124
      }
6125
      $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6126
6127
      if (keyboardChange !== 's') {
6128
        $scope.seconds = pad(seconds);
6129
      }
6130
      $scope.meridian = selected.getHours() < 12 ? meridians[0] : meridians[1];
6131
    }
6132
  }
6133
6134
  function addSecondsToSelected(seconds) {
6135
    selected = addSeconds(selected, seconds);
6136
    refresh();
6137
  }
6138
6139
  function addMinutes(selected, minutes) {
6140
    return addSeconds(selected, minutes*60);
6141
  }
6142
6143
  function addSeconds(date, seconds) {
6144
    var dt = new Date(date.getTime() + seconds * 1000);
6145
    var newDate = new Date(date);
6146
    newDate.setHours(dt.getHours(), dt.getMinutes(), dt.getSeconds());
6147
    return newDate;
6148
  }
6149
6150
  function modelIsEmpty() {
6151
    return ($scope.hours === null || $scope.hours === '') &&
6152
      ($scope.minutes === null || $scope.minutes === '') &&
6153
      (!$scope.showSeconds || $scope.showSeconds && ($scope.seconds === null || $scope.seconds === ''));
6154
  }
6155
6156
  $scope.showSpinners = angular.isDefined($attrs.showSpinners) ?
6157
    $scope.$parent.$eval($attrs.showSpinners) : timepickerConfig.showSpinners;
6158
6159
  $scope.incrementHours = function() {
6160
    if (!$scope.noIncrementHours()) {
6161
      addSecondsToSelected(hourStep * 60 * 60);
6162
    }
6163
  };
6164
6165
  $scope.decrementHours = function() {
6166
    if (!$scope.noDecrementHours()) {
6167
      addSecondsToSelected(-hourStep * 60 * 60);
6168
    }
6169
  };
6170
6171
  $scope.incrementMinutes = function() {
6172
    if (!$scope.noIncrementMinutes()) {
6173
      addSecondsToSelected(minuteStep * 60);
6174
    }
6175
  };
6176
6177
  $scope.decrementMinutes = function() {
6178
    if (!$scope.noDecrementMinutes()) {
6179
      addSecondsToSelected(-minuteStep * 60);
6180
    }
6181
  };
6182
6183
  $scope.incrementSeconds = function() {
6184
    if (!$scope.noIncrementSeconds()) {
6185
      addSecondsToSelected(secondStep);
6186
    }
6187
  };
6188
6189
  $scope.decrementSeconds = function() {
6190
    if (!$scope.noDecrementSeconds()) {
6191
      addSecondsToSelected(-secondStep);
6192
    }
6193
  };
6194
6195
  $scope.toggleMeridian = function() {
6196
    var minutes = getMinutesFromTemplate(),
6197
        hours = getHoursFromTemplate();
6198
6199
    if (!$scope.noToggleMeridian()) {
6200
      if (angular.isDefined(minutes) && angular.isDefined(hours)) {
6201
        addSecondsToSelected(12 * 60 * (selected.getHours() < 12 ? 60 : -60));
6202
      } else {
6203
        $scope.meridian = $scope.meridian === meridians[0] ? meridians[1] : meridians[0];
6204
      }
6205
    }
6206
  };
6207
6208
  $scope.blur = function() {
6209
    ngModelCtrl.$setTouched();
6210
  };
6211
6212
  $scope.$on('$destroy', function() {
6213
    while (watchers.length) {
6214
      watchers.shift()();
6215
    }
6216
  });
6217
}])
6218
6219 View Code Duplication
.directive('uibTimepicker', ['uibTimepickerConfig', function(uibTimepickerConfig) {
6220
  return {
6221
    require: ['uibTimepicker', '?^ngModel'],
6222
    controller: 'UibTimepickerController',
6223
    controllerAs: 'timepicker',
6224
    replace: true,
6225
    scope: {},
6226
    templateUrl: function(element, attrs) {
6227
      return attrs.templateUrl || uibTimepickerConfig.templateUrl;
6228
    },
6229
    link: function(scope, element, attrs, ctrls) {
6230
      var timepickerCtrl = ctrls[0], ngModelCtrl = ctrls[1];
6231
6232
      if (ngModelCtrl) {
6233
        timepickerCtrl.init(ngModelCtrl, element.find('input'));
6234
      }
6235
    }
6236
  };
6237
}]);
6238
6239
angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
6240
6241
/**
6242
 * A helper service that can parse typeahead's syntax (string provided by users)
6243
 * Extracted to a separate service for ease of unit testing
6244
 */
6245 View Code Duplication
  .factory('uibTypeaheadParser', ['$parse', function($parse) {
6246
    //                      00000111000000000000022200000000000000003333333333333330000000000044000
6247
    var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
6248
    return {
6249
      parse: function(input) {
6250
        var match = input.match(TYPEAHEAD_REGEXP);
6251
        if (!match) {
6252
          throw new Error(
6253
            'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
6254
              ' but got "' + input + '".');
6255
        }
6256
6257
        return {
6258
          itemName: match[3],
6259
          source: $parse(match[4]),
6260
          viewMapper: $parse(match[2] || match[1]),
6261
          modelMapper: $parse(match[1])
6262
        };
6263
      }
6264
    };
6265
  }])
6266
6267
  .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
6268 View Code Duplication
    function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
6269
    var HOT_KEYS = [9, 13, 27, 38, 40];
6270
    var eventDebounceTime = 200;
6271
    var modelCtrl, ngModelOptions;
6272
    //SUPPORTED ATTRIBUTES (OPTIONS)
6273
6274
    //minimal no of characters that needs to be entered before typeahead kicks-in
6275
    var minLength = originalScope.$eval(attrs.typeaheadMinLength);
6276
    if (!minLength && minLength !== 0) {
6277
      minLength = 1;
6278
    }
6279
6280
    originalScope.$watch(attrs.typeaheadMinLength, function (newVal) {
6281
        minLength = !newVal && newVal !== 0 ? 1 : newVal;
6282
    });
6283
    
6284
    //minimal wait time after last character typed before typeahead kicks-in
6285
    var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
6286
6287
    //should it restrict model values to the ones selected from the popup only?
6288
    var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
6289
    originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
6290
      isEditable = newVal !== false;
6291
    });
6292
6293
    //binding to a variable that indicates if matches are being retrieved asynchronously
6294
    var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
6295
6296
    //a callback executed when a match is selected
6297
    var onSelectCallback = $parse(attrs.typeaheadOnSelect);
6298
6299
    //should it select highlighted popup value when losing focus?
6300
    var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
6301
6302
    //binding to a variable that indicates if there were no results after the query is completed
6303
    var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
6304
6305
    var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
6306
6307
    var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
6308
6309
    var appendTo = attrs.typeaheadAppendTo ?
6310
      originalScope.$eval(attrs.typeaheadAppendTo) : null;
6311
6312
    var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
6313
6314
    //If input matches an item of the list exactly, select it automatically
6315
    var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
6316
6317
    //binding to a variable that indicates if dropdown is open
6318
    var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
6319
6320
    var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
6321
6322
    //INTERNAL VARIABLES
6323
6324
    //model setter executed upon match selection
6325
    var parsedModel = $parse(attrs.ngModel);
6326
    var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
6327
    var $setModelValue = function(scope, newValue) {
6328
      if (angular.isFunction(parsedModel(originalScope)) &&
6329
        ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
6330
        return invokeModelSetter(scope, {$$$p: newValue});
6331
      }
6332
6333
      return parsedModel.assign(scope, newValue);
6334
    };
6335
6336
    //expressions used by typeahead
6337
    var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
6338
6339
    var hasFocus;
6340
6341
    //Used to avoid bug in iOS webview where iOS keyboard does not fire
6342
    //mousedown & mouseup events
6343
    //Issue #3699
6344
    var selected;
6345
6346
    //create a child scope for the typeahead directive so we are not polluting original scope
6347
    //with typeahead-specific data (matches, query etc.)
6348
    var scope = originalScope.$new();
6349
    var offDestroy = originalScope.$on('$destroy', function() {
6350
      scope.$destroy();
6351
    });
6352
    scope.$on('$destroy', offDestroy);
6353
6354
    // WAI-ARIA
6355
    var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
6356
    element.attr({
6357
      'aria-autocomplete': 'list',
6358
      'aria-expanded': false,
6359
      'aria-owns': popupId
6360
    });
6361
6362
    var inputsContainer, hintInputElem;
6363
    //add read-only input to show hint
6364
    if (showHint) {
6365
      inputsContainer = angular.element('<div></div>');
6366
      inputsContainer.css('position', 'relative');
6367
      element.after(inputsContainer);
6368
      hintInputElem = element.clone();
6369
      hintInputElem.attr('placeholder', '');
6370
      hintInputElem.attr('tabindex', '-1');
6371
      hintInputElem.val('');
6372
      hintInputElem.css({
6373
        'position': 'absolute',
6374
        'top': '0px',
6375
        'left': '0px',
6376
        'border-color': 'transparent',
6377
        'box-shadow': 'none',
6378
        'opacity': 1,
6379
        'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
6380
        'color': '#999'
6381
      });
6382
      element.css({
6383
        'position': 'relative',
6384
        'vertical-align': 'top',
6385
        'background-color': 'transparent'
6386
      });
6387
      inputsContainer.append(hintInputElem);
6388
      hintInputElem.after(element);
6389
    }
6390
6391
    //pop-up element used to display matches
6392
    var popUpEl = angular.element('<div uib-typeahead-popup></div>');
6393
    popUpEl.attr({
6394
      id: popupId,
6395
      matches: 'matches',
6396
      active: 'activeIdx',
6397
      select: 'select(activeIdx, evt)',
6398
      'move-in-progress': 'moveInProgress',
6399
      query: 'query',
6400
      position: 'position',
6401
      'assign-is-open': 'assignIsOpen(isOpen)',
6402
      debounce: 'debounceUpdate'
6403
    });
6404
    //custom item template
6405
    if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
6406
      popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
6407
    }
6408
6409
    if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
6410
      popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
6411
    }
6412
6413
    var resetHint = function() {
6414
      if (showHint) {
6415
        hintInputElem.val('');
6416
      }
6417
    };
6418
6419
    var resetMatches = function() {
6420
      scope.matches = [];
6421
      scope.activeIdx = -1;
6422
      element.attr('aria-expanded', false);
6423
      resetHint();
6424
    };
6425
6426
    var getMatchId = function(index) {
6427
      return popupId + '-option-' + index;
6428
    };
6429
6430
    // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
6431
    // This attribute is added or removed automatically when the `activeIdx` changes.
6432
    scope.$watch('activeIdx', function(index) {
6433
      if (index < 0) {
6434
        element.removeAttr('aria-activedescendant');
6435
      } else {
6436
        element.attr('aria-activedescendant', getMatchId(index));
6437
      }
6438
    });
6439
6440
    var inputIsExactMatch = function(inputValue, index) {
6441
      if (scope.matches.length > index && inputValue) {
6442
        return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
6443
      }
6444
6445
      return false;
6446
    };
6447
6448
    var getMatchesAsync = function(inputValue, evt) {
6449
      var locals = {$viewValue: inputValue};
6450
      isLoadingSetter(originalScope, true);
6451
      isNoResultsSetter(originalScope, false);
6452
      $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
6453
        //it might happen that several async queries were in progress if a user were typing fast
6454
        //but we are interested only in responses that correspond to the current view value
6455
        var onCurrentRequest = inputValue === modelCtrl.$viewValue;
6456
        if (onCurrentRequest && hasFocus) {
6457
          if (matches && matches.length > 0) {
6458
            scope.activeIdx = focusFirst ? 0 : -1;
6459
            isNoResultsSetter(originalScope, false);
6460
            scope.matches.length = 0;
6461
6462
            //transform labels
6463
            for (var i = 0; i < matches.length; i++) {
6464
              locals[parserResult.itemName] = matches[i];
6465
              scope.matches.push({
6466
                id: getMatchId(i),
6467
                label: parserResult.viewMapper(scope, locals),
6468
                model: matches[i]
6469
              });
6470
            }
6471
6472
            scope.query = inputValue;
6473
            //position pop-up with matches - we need to re-calculate its position each time we are opening a window
6474
            //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
6475
            //due to other elements being rendered
6476
            recalculatePosition();
6477
6478
            element.attr('aria-expanded', true);
6479
6480
            //Select the single remaining option if user input matches
6481
            if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
6482
              if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
6483
                $$debounce(function() {
6484
                  scope.select(0, evt);
6485
                }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
6486
              } else {
6487
                scope.select(0, evt);
6488
              }
6489
            }
6490
6491
            if (showHint) {
6492
              var firstLabel = scope.matches[0].label;
6493
              if (angular.isString(inputValue) &&
6494
                inputValue.length > 0 &&
6495
                firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
6496
                hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
6497
              } else {
6498
                hintInputElem.val('');
6499
              }
6500
            }
6501
          } else {
6502
            resetMatches();
6503
            isNoResultsSetter(originalScope, true);
6504
          }
6505
        }
6506
        if (onCurrentRequest) {
6507
          isLoadingSetter(originalScope, false);
6508
        }
6509
      }, function() {
6510
        resetMatches();
6511
        isLoadingSetter(originalScope, false);
6512
        isNoResultsSetter(originalScope, true);
6513
      });
6514
    };
6515
6516
    // bind events only if appendToBody params exist - performance feature
6517
    if (appendToBody) {
6518
      angular.element($window).on('resize', fireRecalculating);
6519
      $document.find('body').on('scroll', fireRecalculating);
6520
    }
6521
6522
    // Declare the debounced function outside recalculating for
6523
    // proper debouncing
6524
    var debouncedRecalculate = $$debounce(function() {
6525
      // if popup is visible
6526
      if (scope.matches.length) {
6527
        recalculatePosition();
6528
      }
6529
6530
      scope.moveInProgress = false;
6531
    }, eventDebounceTime);
6532
6533
    // Default progress type
6534
    scope.moveInProgress = false;
6535
6536
    function fireRecalculating() {
6537
      if (!scope.moveInProgress) {
6538
        scope.moveInProgress = true;
6539
        scope.$digest();
6540
      }
6541
6542
      debouncedRecalculate();
6543
    }
6544
6545
    // recalculate actual position and set new values to scope
6546
    // after digest loop is popup in right position
6547
    function recalculatePosition() {
6548
      scope.position = appendToBody ? $position.offset(element) : $position.position(element);
6549
      scope.position.top += element.prop('offsetHeight');
6550
    }
6551
6552
    //we need to propagate user's query so we can higlight matches
6553
    scope.query = undefined;
6554
6555
    //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
6556
    var timeoutPromise;
6557
6558
    var scheduleSearchWithTimeout = function(inputValue) {
6559
      timeoutPromise = $timeout(function() {
6560
        getMatchesAsync(inputValue);
6561
      }, waitTime);
6562
    };
6563
6564
    var cancelPreviousTimeout = function() {
6565
      if (timeoutPromise) {
6566
        $timeout.cancel(timeoutPromise);
6567
      }
6568
    };
6569
6570
    resetMatches();
6571
6572
    scope.assignIsOpen = function (isOpen) {
6573
      isOpenSetter(originalScope, isOpen);
6574
    };
6575
6576
    scope.select = function(activeIdx, evt) {
6577
      //called from within the $digest() cycle
6578
      var locals = {};
6579
      var model, item;
6580
6581
      selected = true;
6582
      locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
6583
      model = parserResult.modelMapper(originalScope, locals);
6584
      $setModelValue(originalScope, model);
6585
      modelCtrl.$setValidity('editable', true);
6586
      modelCtrl.$setValidity('parse', true);
6587
6588
      onSelectCallback(originalScope, {
6589
        $item: item,
6590
        $model: model,
6591
        $label: parserResult.viewMapper(originalScope, locals),
6592
        $event: evt
6593
      });
6594
6595
      resetMatches();
6596
6597
      //return focus to the input element if a match was selected via a mouse click event
6598
      // use timeout to avoid $rootScope:inprog error
6599
      if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
6600
        $timeout(function() { element[0].focus(); }, 0, false);
6601
      }
6602
    };
6603
6604
    //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
6605
    element.on('keydown', function(evt) {
6606
      //typeahead is open and an "interesting" key was pressed
6607
      if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
6608
        return;
6609
      }
6610
6611
      /**
6612
       * if there's nothing selected (i.e. focusFirst) and enter or tab is hit
6613
       * or
6614
       * shift + tab is pressed to bring focus to the previous element
6615
       * then clear the results
6616
       */
6617
      if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13) || evt.which === 9 && !!evt.shiftKey) {
6618
        resetMatches();
6619
        scope.$digest();
6620
        return;
6621
      }
6622
6623
      evt.preventDefault();
6624
      var target;
6625
      switch (evt.which) {
6626
        case 9:
6627
        case 13:
6628
          scope.$apply(function () {
6629
            if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) {
6630
              $$debounce(function() {
6631
                scope.select(scope.activeIdx, evt);
6632
              }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']);
6633
            } else {
6634
              scope.select(scope.activeIdx, evt);
6635
            }
6636
          });
6637
          break;
6638
        case 27:
6639
          evt.stopPropagation();
6640
6641
          resetMatches();
6642
          originalScope.$digest();
6643
          break;
6644
        case 38:
6645
          scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
6646
          scope.$digest();
6647
          target = popUpEl.find('li')[scope.activeIdx];
6648
          target.parentNode.scrollTop = target.offsetTop;
6649
          break;
6650
        case 40:
6651
          scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
6652
          scope.$digest();
6653
          target = popUpEl.find('li')[scope.activeIdx];
6654
          target.parentNode.scrollTop = target.offsetTop;
6655
          break;
6656
      }
6657
    });
6658
6659
    element.bind('focus', function (evt) {
6660
      hasFocus = true;
6661
      if (minLength === 0 && !modelCtrl.$viewValue) {
6662
        $timeout(function() {
6663
          getMatchesAsync(modelCtrl.$viewValue, evt);
6664
        }, 0);
6665
      }
6666
    });
6667
6668
    element.bind('blur', function(evt) {
6669
      if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
6670
        selected = true;
6671
        scope.$apply(function() {
6672
          if (angular.isObject(scope.debounceUpdate) && angular.isNumber(scope.debounceUpdate.blur)) {
6673
            $$debounce(function() {
6674
              scope.select(scope.activeIdx, evt);
6675
            }, scope.debounceUpdate.blur);
6676
          } else {
6677
            scope.select(scope.activeIdx, evt);
6678
          }
6679
        });
6680
      }
6681
      if (!isEditable && modelCtrl.$error.editable) {
6682
        modelCtrl.$setViewValue();
6683
        // Reset validity as we are clearing
6684
        modelCtrl.$setValidity('editable', true);
6685
        modelCtrl.$setValidity('parse', true);
6686
        element.val('');
6687
      }
6688
      hasFocus = false;
6689
      selected = false;
6690
    });
6691
6692
    // Keep reference to click handler to unbind it.
6693
    var dismissClickHandler = function(evt) {
6694
      // Issue #3973
6695
      // Firefox treats right click as a click on document
6696
      if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
6697
        resetMatches();
6698
        if (!$rootScope.$$phase) {
6699
          originalScope.$digest();
6700
        }
6701
      }
6702
    };
6703
6704
    $document.on('click', dismissClickHandler);
6705
6706
    originalScope.$on('$destroy', function() {
6707
      $document.off('click', dismissClickHandler);
6708
      if (appendToBody || appendTo) {
6709
        $popup.remove();
6710
      }
6711
6712
      if (appendToBody) {
6713
        angular.element($window).off('resize', fireRecalculating);
6714
        $document.find('body').off('scroll', fireRecalculating);
6715
      }
6716
      // Prevent jQuery cache memory leak
6717
      popUpEl.remove();
6718
6719
      if (showHint) {
6720
          inputsContainer.remove();
6721
      }
6722
    });
6723
6724
    var $popup = $compile(popUpEl)(scope);
6725
6726
    if (appendToBody) {
6727
      $document.find('body').append($popup);
6728
    } else if (appendTo) {
6729
      angular.element(appendTo).eq(0).append($popup);
6730
    } else {
6731
      element.after($popup);
6732
    }
6733
6734
    this.init = function(_modelCtrl, _ngModelOptions) {
6735
      modelCtrl = _modelCtrl;
6736
      ngModelOptions = _ngModelOptions;
6737
6738
      scope.debounceUpdate = modelCtrl.$options && $parse(modelCtrl.$options.debounce)(originalScope);
6739
6740
      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
6741
      //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
6742
      modelCtrl.$parsers.unshift(function(inputValue) {
6743
        hasFocus = true;
6744
6745
        if (minLength === 0 || inputValue && inputValue.length >= minLength) {
6746
          if (waitTime > 0) {
6747
            cancelPreviousTimeout();
6748
            scheduleSearchWithTimeout(inputValue);
6749
          } else {
6750
            getMatchesAsync(inputValue);
6751
          }
6752
        } else {
6753
          isLoadingSetter(originalScope, false);
6754
          cancelPreviousTimeout();
6755
          resetMatches();
6756
        }
6757
6758
        if (isEditable) {
6759
          return inputValue;
6760
        }
6761
6762
        if (!inputValue) {
6763
          // Reset in case user had typed something previously.
6764
          modelCtrl.$setValidity('editable', true);
6765
          return null;
6766
        }
6767
6768
        modelCtrl.$setValidity('editable', false);
6769
        return undefined;
6770
      });
6771
6772
      modelCtrl.$formatters.push(function(modelValue) {
6773
        var candidateViewValue, emptyViewValue;
6774
        var locals = {};
6775
6776
        // The validity may be set to false via $parsers (see above) if
6777
        // the model is restricted to selected values. If the model
6778
        // is set manually it is considered to be valid.
6779
        if (!isEditable) {
6780
          modelCtrl.$setValidity('editable', true);
6781
        }
6782
6783
        if (inputFormatter) {
6784
          locals.$model = modelValue;
6785
          return inputFormatter(originalScope, locals);
6786
        }
6787
6788
        //it might happen that we don't have enough info to properly render input value
6789
        //we need to check for this situation and simply return model value if we can't apply custom formatting
6790
        locals[parserResult.itemName] = modelValue;
6791
        candidateViewValue = parserResult.viewMapper(originalScope, locals);
6792
        locals[parserResult.itemName] = undefined;
6793
        emptyViewValue = parserResult.viewMapper(originalScope, locals);
6794
6795
        return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
6796
      });
6797
    };
6798
  }])
6799
6800
  .directive('uibTypeahead', function() {
6801
    return {
6802
      controller: 'UibTypeaheadController',
6803
      require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
6804
      link: function(originalScope, element, attrs, ctrls) {
6805
        ctrls[2].init(ctrls[0], ctrls[1]);
6806
      }
6807
    };
6808
  })
6809
6810 View Code Duplication
  .directive('uibTypeaheadPopup', ['$$debounce', function($$debounce) {
6811
    return {
6812
      scope: {
6813
        matches: '=',
6814
        query: '=',
6815
        active: '=',
6816
        position: '&',
6817
        moveInProgress: '=',
6818
        select: '&',
6819
        assignIsOpen: '&',
6820
        debounce: '&'
6821
      },
6822
      replace: true,
6823
      templateUrl: function(element, attrs) {
6824
        return attrs.popupTemplateUrl || 'uib/template/typeahead/typeahead-popup.html';
6825
      },
6826
      link: function(scope, element, attrs) {
6827
        scope.templateUrl = attrs.templateUrl;
6828
6829
        scope.isOpen = function() {
6830
          var isDropdownOpen = scope.matches.length > 0;
6831
          scope.assignIsOpen({ isOpen: isDropdownOpen });
6832
          return isDropdownOpen;
6833
        };
6834
6835
        scope.isActive = function(matchIdx) {
6836
          return scope.active === matchIdx;
6837
        };
6838
6839
        scope.selectActive = function(matchIdx) {
6840
          scope.active = matchIdx;
6841
        };
6842
6843
        scope.selectMatch = function(activeIdx, evt) {
6844
          var debounce = scope.debounce();
6845
          if (angular.isNumber(debounce) || angular.isObject(debounce)) {
6846
            $$debounce(function() {
6847
              scope.select({activeIdx: activeIdx, evt: evt});
6848
            }, angular.isNumber(debounce) ? debounce : debounce['default']);
6849
          } else {
6850
            scope.select({activeIdx: activeIdx, evt: evt});
6851
          }
6852
        };
6853
      }
6854
    };
6855
  }])
6856
6857
  .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
6858
    return {
6859
      scope: {
6860
        index: '=',
6861
        match: '=',
6862
        query: '='
6863
      },
6864
      link: function(scope, element, attrs) {
6865
        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'uib/template/typeahead/typeahead-match.html';
6866
        $templateRequest(tplUrl).then(function(tplContent) {
6867
          var tplEl = angular.element(tplContent.trim());
6868
          element.replaceWith(tplEl);
6869
          $compile(tplEl)(scope);
6870
        });
6871
      }
6872
    };
6873
  }])
6874
6875 View Code Duplication
  .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
6876
    var isSanitizePresent;
6877
    isSanitizePresent = $injector.has('$sanitize');
6878
6879
    function escapeRegexp(queryToEscape) {
6880
      // Regex: capture the whole query string and replace it with the string that will be used to match
6881
      // the results, for example if the capture is "a" the result will be \a
6882
      return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
6883
    }
6884
6885
    function containsHtml(matchItem) {
6886
      return /<.*>/g.test(matchItem);
6887
    }
6888
6889
    return function(matchItem, query) {
6890
      if (!isSanitizePresent && containsHtml(matchItem)) {
6891
        $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
6892
      }
6893
      matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
6894
      if (!isSanitizePresent) {
6895
        matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
6896
      }
6897
      return matchItem;
6898
    };
6899
  }]);
6900
angular.module('ui.bootstrap.carousel').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibCarouselCss && angular.element(document).find('head').prepend('<style type="text/css">.ng-animate.item:not(.left):not(.right){-webkit-transition:0s ease-in-out left;transition:0s ease-in-out left}</style>'); angular.$$uibCarouselCss = true; });
6901
angular.module('ui.bootstrap.datepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker .uib-title{width:100%;}.uib-day button,.uib-month button,.uib-year button{min-width:100%;}.uib-left,.uib-right{width:100%}</style>'); angular.$$uibDatepickerCss = true; });
6902
angular.module('ui.bootstrap.position').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibPositionCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-position-measure{display:block !important;visibility:hidden !important;position:absolute !important;top:-9999px !important;left:-9999px !important;}.uib-position-scrollbar-measure{position:absolute !important;top:-9999px !important;width:50px !important;height:50px !important;overflow:scroll !important;}.uib-position-body-scrollbar-measure{overflow:scroll !important;}</style>'); angular.$$uibPositionCss = true; });
6903
angular.module('ui.bootstrap.datepickerPopup').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibDatepickerpopupCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-datepicker-popup.dropdown-menu{display:block;float:none;margin:0;}.uib-button-bar{padding:10px 9px 2px;}</style>'); angular.$$uibDatepickerpopupCss = true; });
6904
angular.module('ui.bootstrap.tooltip').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTooltipCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-tooltip-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-html-popup].tooltip.right-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.top-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-left > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.bottom-right > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.left-bottom > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-top > .tooltip-arrow,[uib-tooltip-template-popup].tooltip.right-bottom > .tooltip-arrow,[uib-popover-popup].popover.top-left > .arrow,[uib-popover-popup].popover.top-right > .arrow,[uib-popover-popup].popover.bottom-left > .arrow,[uib-popover-popup].popover.bottom-right > .arrow,[uib-popover-popup].popover.left-top > .arrow,[uib-popover-popup].popover.left-bottom > .arrow,[uib-popover-popup].popover.right-top > .arrow,[uib-popover-popup].popover.right-bottom > .arrow,[uib-popover-html-popup].popover.top-left > .arrow,[uib-popover-html-popup].popover.top-right > .arrow,[uib-popover-html-popup].popover.bottom-left > .arrow,[uib-popover-html-popup].popover.bottom-right > .arrow,[uib-popover-html-popup].popover.left-top > .arrow,[uib-popover-html-popup].popover.left-bottom > .arrow,[uib-popover-html-popup].popover.right-top > .arrow,[uib-popover-html-popup].popover.right-bottom > .arrow,[uib-popover-template-popup].popover.top-left > .arrow,[uib-popover-template-popup].popover.top-right > .arrow,[uib-popover-template-popup].popover.bottom-left > .arrow,[uib-popover-template-popup].popover.bottom-right > .arrow,[uib-popover-template-popup].popover.left-top > .arrow,[uib-popover-template-popup].popover.left-bottom > .arrow,[uib-popover-template-popup].popover.right-top > .arrow,[uib-popover-template-popup].popover.right-bottom > .arrow{top:auto;bottom:auto;left:auto;right:auto;margin:0;}[uib-popover-popup].popover,[uib-popover-html-popup].popover,[uib-popover-template-popup].popover{display:block !important;}</style>'); angular.$$uibTooltipCss = true; });
6905
angular.module('ui.bootstrap.timepicker').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTimepickerCss && angular.element(document).find('head').prepend('<style type="text/css">.uib-time input{width:50px;}</style>'); angular.$$uibTimepickerCss = true; });
6906
angular.module('ui.bootstrap.typeahead').run(function() {!angular.$$csp().noInlineStyle && !angular.$$uibTypeaheadCss && angular.element(document).find('head').prepend('<style type="text/css">[uib-typeahead-popup].dropdown-menu{display:block;}</style>'); angular.$$uibTypeaheadCss = true; });