Total Complexity | 1402 |
Complexity/F | 2.11 |
Lines of Code | 6900 |
Function Count | 666 |
Duplicated Lines | 6158 |
Ratio | 89.25 % |
Changes | 0 |
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:
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 | /* |
||
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) { |
||
|
|||
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; |
||
706 | var direction = element.data(SLIDE_DIRECTION); |
||
707 | var directionClass = direction === 'next' ? 'left' : 'right'; |
||
708 | var removeClassFn = removeClass.bind(this, element, |
||
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; |
||
718 | }; |
||
719 | } |
||
720 | done(); |
||
721 | }, |
||
722 | beforeRemoveClass: function (element, className, done) { |
||
723 | if (className === 'active') { |
||
724 | var stopped = false; |
||
725 | var direction = element.data(SLIDE_DIRECTION); |
||
726 | var directionClass = direction === 'next' ? 'left' : 'right'; |
||
727 | var removeClassFn = removeClass.bind(this, element, directionClass, done); |
||
728 | |||
729 | $animateCss(element, {addClass: directionClass}) |
||
730 | .start() |
||
731 | .done(removeClassFn); |
||
732 | |||
733 | return function() { |
||
734 | stopped = true; |
||
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 = {}; |
||
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; |
||
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; |
||
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 = {}, |
||
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) { |
||
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; |
||
3769 | removeAfterAnimate(backdropDomEl, backdropScope, function() { |
||
3770 | backdropScopeRef = null; |
||
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 | } |
||
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; |
||
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); |
||
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); |
||
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; }); |