Razique Mahroua
2019-11-28 1759c24ad2d2b35ec5c756e3dd3a60185fe944b7
commit | author | age
1759c2 1 /**
RM 2  * angular-strap
3  * @version v2.0.3 - 2014-05-30
4  * @link http://mgcrea.github.io/angular-strap
5  * @author Olivier Louvignes (olivier@mg-crea.com)
6  * @license MIT License, http://www.opensource.org/licenses/MIT
7  */
8 (function(window, document, undefined) {
9 'use strict';
10 // Source: module.js
11 angular.module('mgcrea.ngStrap', [
12   'mgcrea.ngStrap.modal',
13   'mgcrea.ngStrap.aside',
14   'mgcrea.ngStrap.alert',
15   'mgcrea.ngStrap.button',
16   'mgcrea.ngStrap.select',
17   'mgcrea.ngStrap.datepicker',
18   'mgcrea.ngStrap.timepicker',
19   'mgcrea.ngStrap.navbar',
20   'mgcrea.ngStrap.tooltip',
21   'mgcrea.ngStrap.popover',
22   'mgcrea.ngStrap.dropdown',
23   'mgcrea.ngStrap.typeahead',
24   'mgcrea.ngStrap.scrollspy',
25   'mgcrea.ngStrap.affix',
26   'mgcrea.ngStrap.tab'
27 ]);
28
29 // Source: affix.js
30 angular.module('mgcrea.ngStrap.affix', [
31   'mgcrea.ngStrap.helpers.dimensions',
32   'mgcrea.ngStrap.helpers.debounce'
33 ]).provider('$affix', function () {
34   var defaults = this.defaults = { offsetTop: 'auto' };
35   this.$get = [
36     '$window',
37     'debounce',
38     'dimensions',
39     function ($window, debounce, dimensions) {
40       var bodyEl = angular.element($window.document.body);
41       var windowEl = angular.element($window);
42       function AffixFactory(element, config) {
43         var $affix = {};
44         // Common vars
45         var options = angular.extend({}, defaults, config);
46         var targetEl = options.target;
47         // Initial private vars
48         var reset = 'affix affix-top affix-bottom', initialAffixTop = 0, initialOffsetTop = 0, offsetTop = 0, offsetBottom = 0, affixed = null, unpin = null;
49         var parent = element.parent();
50         // Options: custom parent
51         if (options.offsetParent) {
52           if (options.offsetParent.match(/^\d+$/)) {
53             for (var i = 0; i < options.offsetParent * 1 - 1; i++) {
54               parent = parent.parent();
55             }
56           } else {
57             parent = angular.element(options.offsetParent);
58           }
59         }
60         $affix.init = function () {
61           $affix.$parseOffsets();
62           initialOffsetTop = dimensions.offset(element[0]).top + initialAffixTop;
63           // Bind events
64           targetEl.on('scroll', $affix.checkPosition);
65           targetEl.on('click', $affix.checkPositionWithEventLoop);
66           windowEl.on('resize', $affix.$debouncedOnResize);
67           // Both of these checkPosition() calls are necessary for the case where
68           // the user hits refresh after scrolling to the bottom of the page.
69           $affix.checkPosition();
70           $affix.checkPositionWithEventLoop();
71         };
72         $affix.destroy = function () {
73           // Unbind events
74           targetEl.off('scroll', $affix.checkPosition);
75           targetEl.off('click', $affix.checkPositionWithEventLoop);
76           windowEl.off('resize', $affix.$debouncedOnResize);
77         };
78         $affix.checkPositionWithEventLoop = function () {
79           setTimeout($affix.checkPosition, 1);
80         };
81         $affix.checkPosition = function () {
82           // if (!this.$element.is(':visible')) return
83           var scrollTop = getScrollTop();
84           var position = dimensions.offset(element[0]);
85           var elementHeight = dimensions.height(element[0]);
86           // Get required affix class according to position
87           var affix = getRequiredAffixClass(unpin, position, elementHeight);
88           // Did affix status changed this last check?
89           if (affixed === affix)
90             return;
91           affixed = affix;
92           // Add proper affix class
93           element.removeClass(reset).addClass('affix' + (affix !== 'middle' ? '-' + affix : ''));
94           if (affix === 'top') {
95             unpin = null;
96             element.css('position', options.offsetParent ? '' : 'relative');
97             element.css('top', '');
98           } else if (affix === 'bottom') {
99             if (options.offsetUnpin) {
100               unpin = -(options.offsetUnpin * 1);
101             } else {
102               // Calculate unpin threshold when affixed to bottom.
103               // Hopefully the browser scrolls pixel by pixel.
104               unpin = position.top - scrollTop;
105             }
106             element.css('position', options.offsetParent ? '' : 'relative');
107             element.css('top', options.offsetParent ? '' : bodyEl[0].offsetHeight - offsetBottom - elementHeight - initialOffsetTop + 'px');
108           } else {
109             // affix === 'middle'
110             unpin = null;
111             element.css('position', 'fixed');
112             element.css('top', initialAffixTop + 'px');
113           }
114         };
115         $affix.$onResize = function () {
116           $affix.$parseOffsets();
117           $affix.checkPosition();
118         };
119         $affix.$debouncedOnResize = debounce($affix.$onResize, 50);
120         $affix.$parseOffsets = function () {
121           // Reset position to calculate correct offsetTop
122           element.css('position', options.offsetParent ? '' : 'relative');
123           if (options.offsetTop) {
124             if (options.offsetTop === 'auto') {
125               options.offsetTop = '+0';
126             }
127             if (options.offsetTop.match(/^[-+]\d+$/)) {
128               initialAffixTop = -options.offsetTop * 1;
129               if (options.offsetParent) {
130                 offsetTop = dimensions.offset(parent[0]).top + options.offsetTop * 1;
131               } else {
132                 offsetTop = dimensions.offset(element[0]).top - dimensions.css(element[0], 'marginTop', true) + options.offsetTop * 1;
133               }
134             } else {
135               offsetTop = options.offsetTop * 1;
136             }
137           }
138           if (options.offsetBottom) {
139             if (options.offsetParent && options.offsetBottom.match(/^[-+]\d+$/)) {
140               // add 1 pixel due to rounding problems...
141               offsetBottom = getScrollHeight() - (dimensions.offset(parent[0]).top + dimensions.height(parent[0])) + options.offsetBottom * 1 + 1;
142             } else {
143               offsetBottom = options.offsetBottom * 1;
144             }
145           }
146         };
147         // Private methods
148         function getRequiredAffixClass(unpin, position, elementHeight) {
149           var scrollTop = getScrollTop();
150           var scrollHeight = getScrollHeight();
151           if (scrollTop <= offsetTop) {
152             return 'top';
153           } else if (unpin !== null && scrollTop + unpin <= position.top) {
154             return 'middle';
155           } else if (offsetBottom !== null && position.top + elementHeight + initialAffixTop >= scrollHeight - offsetBottom) {
156             return 'bottom';
157           } else {
158             return 'middle';
159           }
160         }
161         function getScrollTop() {
162           return targetEl[0] === $window ? $window.pageYOffset : targetEl[0] === $window;
163         }
164         function getScrollHeight() {
165           return targetEl[0] === $window ? $window.document.body.scrollHeight : targetEl[0].scrollHeight;
166         }
167         $affix.init();
168         return $affix;
169       }
170       return AffixFactory;
171     }
172   ];
173 }).directive('bsAffix', [
174   '$affix',
175   '$window',
176   function ($affix, $window) {
177     return {
178       restrict: 'EAC',
179       require: '^?bsAffixTarget',
180       link: function postLink(scope, element, attr, affixTarget) {
181         var options = {
182             scope: scope,
183             offsetTop: 'auto',
184             target: affixTarget ? affixTarget.$element : angular.element($window)
185           };
186         angular.forEach([
187           'offsetTop',
188           'offsetBottom',
189           'offsetParent',
190           'offsetUnpin'
191         ], function (key) {
192           if (angular.isDefined(attr[key]))
193             options[key] = attr[key];
194         });
195         var affix = $affix(element, options);
196         scope.$on('$destroy', function () {
197           options = null;
198           affix = null;
199         });
200       }
201     };
202   }
203 ]).directive('bsAffixTarget', function () {
204   return {
205     controller: [
206       '$element',
207       function ($element) {
208         this.$element = $element;
209       }
210     ]
211   };
212 });
213
214 // Source: alert.js
215 // @BUG: following snippet won't compile correctly
216 // @TODO: submit issue to core
217 // '<span ng-if="title"><strong ng-bind="title"></strong>&nbsp;</span><span ng-bind-html="content"></span>' +
218 angular.module('mgcrea.ngStrap.alert', ['mgcrea.ngStrap.modal']).provider('$alert', function () {
219   var defaults = this.defaults = {
220       animation: 'am-fade',
221       prefixClass: 'alert',
222       placement: null,
223       template: 'alert/alert.tpl.html',
224       container: false,
225       element: null,
226       backdrop: false,
227       keyboard: true,
228       show: true,
229       duration: false,
230       type: false,
231       dismissable: true
232     };
233   this.$get = [
234     '$modal',
235     '$timeout',
236     function ($modal, $timeout) {
237       function AlertFactory(config) {
238         var $alert = {};
239         // Common vars
240         var options = angular.extend({}, defaults, config);
241         $alert = $modal(options);
242         // Support scope as string options [/*title, content, */ type, dismissable]
243         $alert.$scope.dismissable = !!options.dismissable;
244         if (options.type) {
245           $alert.$scope.type = options.type;
246         }
247         // Support auto-close duration
248         var show = $alert.show;
249         if (options.duration) {
250           $alert.show = function () {
251             show();
252             $timeout(function () {
253               $alert.hide();
254             }, options.duration * 1000);
255           };
256         }
257         return $alert;
258       }
259       return AlertFactory;
260     }
261   ];
262 }).directive('bsAlert', [
263   '$window',
264   '$location',
265   '$sce',
266   '$alert',
267   function ($window, $location, $sce, $alert) {
268     var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
269     return {
270       restrict: 'EAC',
271       scope: true,
272       link: function postLink(scope, element, attr, transclusion) {
273         // Directive options
274         var options = {
275             scope: scope,
276             element: element,
277             show: false
278           };
279         angular.forEach([
280           'template',
281           'placement',
282           'keyboard',
283           'html',
284           'container',
285           'animation',
286           'duration',
287           'dismissable'
288         ], function (key) {
289           if (angular.isDefined(attr[key]))
290             options[key] = attr[key];
291         });
292         // Support scope as data-attrs
293         angular.forEach([
294           'title',
295           'content',
296           'type'
297         ], function (key) {
298           attr[key] && attr.$observe(key, function (newValue, oldValue) {
299             scope[key] = $sce.trustAsHtml(newValue);
300           });
301         });
302         // Support scope as an object
303         attr.bsAlert && scope.$watch(attr.bsAlert, function (newValue, oldValue) {
304           if (angular.isObject(newValue)) {
305             angular.extend(scope, newValue);
306           } else {
307             scope.content = newValue;
308           }
309         }, true);
310         // Initialize alert
311         var alert = $alert(options);
312         // Trigger
313         element.on(attr.trigger || 'click', alert.toggle);
314         // Garbage collection
315         scope.$on('$destroy', function () {
316           alert.destroy();
317           options = null;
318           alert = null;
319         });
320       }
321     };
322   }
323 ]);
324
325 // Source: button.js
326 angular.module('mgcrea.ngStrap.button', []).provider('$button', function () {
327   var defaults = this.defaults = {
328       activeClass: 'active',
329       toggleEvent: 'click'
330     };
331   this.$get = function () {
332     return { defaults: defaults };
333   };
334 }).directive('bsCheckboxGroup', function () {
335   return {
336     restrict: 'A',
337     require: 'ngModel',
338     compile: function postLink(element, attr) {
339       element.attr('data-toggle', 'buttons');
340       element.removeAttr('ng-model');
341       var children = element[0].querySelectorAll('input[type="checkbox"]');
342       angular.forEach(children, function (child) {
343         var childEl = angular.element(child);
344         childEl.attr('bs-checkbox', '');
345         childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));
346       });
347     }
348   };
349 }).directive('bsCheckbox', [
350   '$button',
351   '$$rAF',
352   function ($button, $$rAF) {
353     var defaults = $button.defaults;
354     var constantValueRegExp = /^(true|false|\d+)$/;
355     return {
356       restrict: 'A',
357       require: 'ngModel',
358       link: function postLink(scope, element, attr, controller) {
359         var options = defaults;
360         // Support label > input[type="checkbox"]
361         var isInput = element[0].nodeName === 'INPUT';
362         var activeElement = isInput ? element.parent() : element;
363         var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;
364         if (constantValueRegExp.test(attr.trueValue)) {
365           trueValue = scope.$eval(attr.trueValue);
366         }
367         var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;
368         if (constantValueRegExp.test(attr.falseValue)) {
369           falseValue = scope.$eval(attr.falseValue);
370         }
371         // Parse exotic values
372         var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';
373         if (hasExoticValues) {
374           controller.$parsers.push(function (viewValue) {
375             // console.warn('$parser', element.attr('ng-model'), 'viewValue', viewValue);
376             return viewValue ? trueValue : falseValue;
377           });
378           // Fix rendering for exotic values
379           scope.$watch(attr.ngModel, function (newValue, oldValue) {
380             controller.$render();
381           });
382         }
383         // model -> view
384         controller.$render = function () {
385           // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
386           var isActive = angular.equals(controller.$modelValue, trueValue);
387           $$rAF(function () {
388             if (isInput)
389               element[0].checked = isActive;
390             activeElement.toggleClass(options.activeClass, isActive);
391           });
392         };
393         // view -> model
394         element.bind(options.toggleEvent, function () {
395           scope.$apply(function () {
396             // console.warn('!click', element.attr('ng-model'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
397             if (!isInput) {
398               controller.$setViewValue(!activeElement.hasClass('active'));
399             }
400             if (!hasExoticValues) {
401               controller.$render();
402             }
403           });
404         });
405       }
406     };
407   }
408 ]).directive('bsRadioGroup', function () {
409   return {
410     restrict: 'A',
411     require: 'ngModel',
412     compile: function postLink(element, attr) {
413       element.attr('data-toggle', 'buttons');
414       element.removeAttr('ng-model');
415       var children = element[0].querySelectorAll('input[type="radio"]');
416       angular.forEach(children, function (child) {
417         angular.element(child).attr('bs-radio', '');
418         angular.element(child).attr('ng-model', attr.ngModel);
419       });
420     }
421   };
422 }).directive('bsRadio', [
423   '$button',
424   '$$rAF',
425   function ($button, $$rAF) {
426     var defaults = $button.defaults;
427     var constantValueRegExp = /^(true|false|\d+)$/;
428     return {
429       restrict: 'A',
430       require: 'ngModel',
431       link: function postLink(scope, element, attr, controller) {
432         var options = defaults;
433         // Support `label > input[type="radio"]` markup
434         var isInput = element[0].nodeName === 'INPUT';
435         var activeElement = isInput ? element.parent() : element;
436         var value = constantValueRegExp.test(attr.value) ? scope.$eval(attr.value) : attr.value;
437         // model -> view
438         controller.$render = function () {
439           // console.warn('$render', element.attr('value'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
440           var isActive = angular.equals(controller.$modelValue, value);
441           $$rAF(function () {
442             if (isInput)
443               element[0].checked = isActive;
444             activeElement.toggleClass(options.activeClass, isActive);
445           });
446         };
447         // view -> model
448         element.bind(options.toggleEvent, function () {
449           scope.$apply(function () {
450             // console.warn('!click', element.attr('value'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
451             controller.$setViewValue(value);
452             controller.$render();
453           });
454         });
455       }
456     };
457   }
458 ]);
459
460 // Source: aside.js
461 angular.module('mgcrea.ngStrap.aside', ['mgcrea.ngStrap.modal']).provider('$aside', function () {
462   var defaults = this.defaults = {
463       animation: 'am-fade-and-slide-right',
464       prefixClass: 'aside',
465       placement: 'right',
466       template: 'aside/aside.tpl.html',
467       contentTemplate: false,
468       container: false,
469       element: null,
470       backdrop: true,
471       keyboard: true,
472       html: false,
473       show: true
474     };
475   this.$get = [
476     '$modal',
477     function ($modal) {
478       function AsideFactory(config) {
479         var $aside = {};
480         // Common vars
481         var options = angular.extend({}, defaults, config);
482         $aside = $modal(options);
483         return $aside;
484       }
485       return AsideFactory;
486     }
487   ];
488 }).directive('bsAside', [
489   '$window',
490   '$sce',
491   '$aside',
492   function ($window, $sce, $aside) {
493     var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
494     return {
495       restrict: 'EAC',
496       scope: true,
497       link: function postLink(scope, element, attr, transclusion) {
498         // Directive options
499         var options = {
500             scope: scope,
501             element: element,
502             show: false
503           };
504         angular.forEach([
505           'template',
506           'contentTemplate',
507           'placement',
508           'backdrop',
509           'keyboard',
510           'html',
511           'container',
512           'animation'
513         ], function (key) {
514           if (angular.isDefined(attr[key]))
515             options[key] = attr[key];
516         });
517         // Support scope as data-attrs
518         angular.forEach([
519           'title',
520           'content'
521         ], function (key) {
522           attr[key] && attr.$observe(key, function (newValue, oldValue) {
523             scope[key] = $sce.trustAsHtml(newValue);
524           });
525         });
526         // Support scope as an object
527         attr.bsAside && scope.$watch(attr.bsAside, function (newValue, oldValue) {
528           if (angular.isObject(newValue)) {
529             angular.extend(scope, newValue);
530           } else {
531             scope.content = newValue;
532           }
533         }, true);
534         // Initialize aside
535         var aside = $aside(options);
536         // Trigger
537         element.on(attr.trigger || 'click', aside.toggle);
538         // Garbage collection
539         scope.$on('$destroy', function () {
540           aside.destroy();
541           options = null;
542           aside = null;
543         });
544       }
545     };
546   }
547 ]);
548
549 // Source: datepicker.js
550 angular.module('mgcrea.ngStrap.datepicker', [
551   'mgcrea.ngStrap.helpers.dateParser',
552   'mgcrea.ngStrap.tooltip'
553 ]).provider('$datepicker', function () {
554   var defaults = this.defaults = {
555       animation: 'am-fade',
556       prefixClass: 'datepicker',
557       placement: 'bottom-left',
558       template: 'datepicker/datepicker.tpl.html',
559       trigger: 'focus',
560       container: false,
561       keyboard: true,
562       html: false,
563       delay: 0,
564       useNative: false,
565       dateType: 'date',
566       dateFormat: 'shortDate',
567       modelDateFormat: null,
568       dayFormat: 'dd',
569       strictFormat: false,
570       autoclose: false,
571       minDate: -Infinity,
572       maxDate: +Infinity,
573       startView: 0,
574       minView: 0,
575       startWeek: 0,
576       iconLeft: 'glyphicon glyphicon-chevron-left',
577       iconRight: 'glyphicon glyphicon-chevron-right'
578     };
579   this.$get = [
580     '$window',
581     '$document',
582     '$rootScope',
583     '$sce',
584     '$locale',
585     'dateFilter',
586     'datepickerViews',
587     '$tooltip',
588     function ($window, $document, $rootScope, $sce, $locale, dateFilter, datepickerViews, $tooltip) {
589       var bodyEl = angular.element($window.document.body);
590       var isTouch = 'createTouch' in $window.document;
591       var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
592       if (!defaults.lang)
593         defaults.lang = $locale.id;
594       function DatepickerFactory(element, controller, config) {
595         var $datepicker = $tooltip(element, angular.extend({}, defaults, config));
596         var parentScope = config.scope;
597         var options = $datepicker.$options;
598         var scope = $datepicker.$scope;
599         if (options.startView)
600           options.startView -= options.minView;
601         // View vars
602         var pickerViews = datepickerViews($datepicker);
603         $datepicker.$views = pickerViews.views;
604         var viewDate = pickerViews.viewDate;
605         scope.$mode = options.startView;
606         scope.$iconLeft = options.iconLeft;
607         scope.$iconRight = options.iconRight;
608         var $picker = $datepicker.$views[scope.$mode];
609         // Scope methods
610         scope.$select = function (date) {
611           $datepicker.select(date);
612         };
613         scope.$selectPane = function (value) {
614           $datepicker.$selectPane(value);
615         };
616         scope.$toggleMode = function () {
617           $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);
618         };
619         // Public methods
620         $datepicker.update = function (date) {
621           // console.warn('$datepicker.update() newValue=%o', date);
622           if (angular.isDate(date) && !isNaN(date.getTime())) {
623             $datepicker.$date = date;
624             $picker.update.call($picker, date);
625           }
626           // Build only if pristine
627           $datepicker.$build(true);
628         };
629         $datepicker.select = function (date, keep) {
630           // console.warn('$datepicker.select', date, scope.$mode);
631           if (!angular.isDate(controller.$dateValue))
632             controller.$dateValue = new Date(date);
633           controller.$dateValue.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
634           if (!scope.$mode || keep) {
635             controller.$setViewValue(controller.$dateValue);
636             controller.$render();
637             if (options.autoclose && !keep) {
638               $datepicker.hide(true);
639             }
640           } else {
641             angular.extend(viewDate, {
642               year: date.getFullYear(),
643               month: date.getMonth(),
644               date: date.getDate()
645             });
646             $datepicker.setMode(scope.$mode - 1);
647             $datepicker.$build();
648           }
649         };
650         $datepicker.setMode = function (mode) {
651           // console.warn('$datepicker.setMode', mode);
652           scope.$mode = mode;
653           $picker = $datepicker.$views[scope.$mode];
654           $datepicker.$build();
655         };
656         // Protected methods
657         $datepicker.$build = function (pristine) {
658           // console.warn('$datepicker.$build() viewDate=%o', viewDate);
659           if (pristine === true && $picker.built)
660             return;
661           if (pristine === false && !$picker.built)
662             return;
663           $picker.build.call($picker);
664         };
665         $datepicker.$updateSelected = function () {
666           for (var i = 0, l = scope.rows.length; i < l; i++) {
667             angular.forEach(scope.rows[i], updateSelected);
668           }
669         };
670         $datepicker.$isSelected = function (date) {
671           return $picker.isSelected(date);
672         };
673         $datepicker.$selectPane = function (value) {
674           var steps = $picker.steps;
675           var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, viewDate.date + (steps.day || 0) * value));
676           angular.extend(viewDate, {
677             year: targetDate.getUTCFullYear(),
678             month: targetDate.getUTCMonth(),
679             date: targetDate.getUTCDate()
680           });
681           $datepicker.$build();
682         };
683         $datepicker.$onMouseDown = function (evt) {
684           // Prevent blur on mousedown on .dropdown-menu
685           evt.preventDefault();
686           evt.stopPropagation();
687           // Emulate click for mobile devices
688           if (isTouch) {
689             var targetEl = angular.element(evt.target);
690             if (targetEl[0].nodeName.toLowerCase() !== 'button') {
691               targetEl = targetEl.parent();
692             }
693             targetEl.triggerHandler('click');
694           }
695         };
696         $datepicker.$onKeyDown = function (evt) {
697           if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
698             return;
699           evt.preventDefault();
700           evt.stopPropagation();
701           if (evt.keyCode === 13) {
702             if (!scope.$mode) {
703               return $datepicker.hide(true);
704             } else {
705               return scope.$apply(function () {
706                 $datepicker.setMode(scope.$mode - 1);
707               });
708             }
709           }
710           // Navigate with keyboard
711           $picker.onKeyDown(evt);
712           parentScope.$digest();
713         };
714         // Private
715         function updateSelected(el) {
716           el.selected = $datepicker.$isSelected(el.date);
717         }
718         function focusElement() {
719           element[0].focus();
720         }
721         // Overrides
722         var _init = $datepicker.init;
723         $datepicker.init = function () {
724           if (isNative && options.useNative) {
725             element.prop('type', 'date');
726             element.css('-webkit-appearance', 'textfield');
727             return;
728           } else if (isTouch) {
729             element.prop('type', 'text');
730             element.attr('readonly', 'true');
731             element.on('click', focusElement);
732           }
733           _init();
734         };
735         var _destroy = $datepicker.destroy;
736         $datepicker.destroy = function () {
737           if (isNative && options.useNative) {
738             element.off('click', focusElement);
739           }
740           _destroy();
741         };
742         var _show = $datepicker.show;
743         $datepicker.show = function () {
744           _show();
745           setTimeout(function () {
746             $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
747             if (options.keyboard) {
748               element.on('keydown', $datepicker.$onKeyDown);
749             }
750           });
751         };
752         var _hide = $datepicker.hide;
753         $datepicker.hide = function (blur) {
754           $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
755           if (options.keyboard) {
756             element.off('keydown', $datepicker.$onKeyDown);
757           }
758           _hide(blur);
759         };
760         return $datepicker;
761       }
762       DatepickerFactory.defaults = defaults;
763       return DatepickerFactory;
764     }
765   ];
766 }).directive('bsDatepicker', [
767   '$window',
768   '$parse',
769   '$q',
770   '$locale',
771   'dateFilter',
772   '$datepicker',
773   '$dateParser',
774   '$timeout',
775   function ($window, $parse, $q, $locale, dateFilter, $datepicker, $dateParser, $timeout) {
776     var defaults = $datepicker.defaults;
777     var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
778     var isNumeric = function (n) {
779       return !isNaN(parseFloat(n)) && isFinite(n);
780     };
781     return {
782       restrict: 'EAC',
783       require: 'ngModel',
784       link: function postLink(scope, element, attr, controller) {
785         // Directive options
786         var options = {
787             scope: scope,
788             controller: controller
789           };
790         angular.forEach([
791           'placement',
792           'container',
793           'delay',
794           'trigger',
795           'keyboard',
796           'html',
797           'animation',
798           'template',
799           'autoclose',
800           'dateType',
801           'dateFormat',
802           'modelDateFormat',
803           'dayFormat',
804           'strictFormat',
805           'startWeek',
806           'useNative',
807           'lang',
808           'startView',
809           'minView'
810         ], function (key) {
811           if (angular.isDefined(attr[key]))
812             options[key] = attr[key];
813         });
814         // Initialize datepicker
815         if (isNative && options.useNative)
816           options.dateFormat = 'yyyy-MM-dd';
817         var datepicker = $datepicker(element, controller, options);
818         options = datepicker.$options;
819         // Observe attributes for changes
820         angular.forEach([
821           'minDate',
822           'maxDate'
823         ], function (key) {
824           // console.warn('attr.$observe(%s)', key, attr[key]);
825           angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
826             // console.warn('attr.$observe(%s)=%o', key, newValue);
827             if (newValue === 'today') {
828               var today = new Date();
829               datepicker.$options[key] = +new Date(today.getFullYear(), today.getMonth(), today.getDate() + (key === 'maxDate' ? 1 : 0), 0, 0, 0, key === 'minDate' ? 0 : -1);
830             } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
831               // Support {{ dateObj }}
832               datepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
833             } else if (isNumeric(newValue)) {
834               datepicker.$options[key] = +new Date(parseInt(newValue, 10));
835             } else {
836               datepicker.$options[key] = +new Date(newValue);
837             }
838             // Build only if dirty
839             !isNaN(datepicker.$options[key]) && datepicker.$build(false);
840           });
841         });
842         // Watch model for changes
843         scope.$watch(attr.ngModel, function (newValue, oldValue) {
844           datepicker.update(controller.$dateValue);
845         }, true);
846         var dateParser = $dateParser({
847             format: options.dateFormat,
848             lang: options.lang,
849             strict: options.strictFormat
850           });
851         // viewValue -> $parsers -> modelValue
852         controller.$parsers.unshift(function (viewValue) {
853           // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
854           // Null values should correctly reset the model value & validity
855           if (!viewValue) {
856             controller.$setValidity('date', true);
857             return;
858           }
859           var parsedDate = dateParser.parse(viewValue, controller.$dateValue);
860           if (!parsedDate || isNaN(parsedDate.getTime())) {
861             controller.$setValidity('date', false);
862             return;
863           } else {
864             var isMinValid = isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate;
865             var isMaxValid = isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate;
866             var isValid = isMinValid && isMaxValid;
867             controller.$setValidity('date', isValid);
868             controller.$setValidity('min', isMinValid);
869             controller.$setValidity('max', isMaxValid);
870             // Only update the model when we have a valid date
871             if (isValid)
872               controller.$dateValue = parsedDate;
873           }
874           if (options.dateType === 'string') {
875             return dateFilter(parsedDate, options.modelDateFormat || options.dateFormat);
876           } else if (options.dateType === 'number') {
877             return controller.$dateValue.getTime();
878           } else if (options.dateType === 'iso') {
879             return controller.$dateValue.toISOString();
880           } else {
881             return new Date(controller.$dateValue);
882           }
883         });
884         // modelValue -> $formatters -> viewValue
885         controller.$formatters.push(function (modelValue) {
886           // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
887           var date;
888           if (angular.isUndefined(modelValue) || modelValue === null) {
889             date = NaN;
890           } else if (angular.isDate(modelValue)) {
891             date = modelValue;
892           } else if (options.dateType === 'string') {
893             date = dateParser.parse(modelValue, null, options.modelDateFormat);
894           } else {
895             date = new Date(modelValue);
896           }
897           // Setup default value?
898           // if(isNaN(date.getTime())) {
899           //   var today = new Date();
900           //   date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0);
901           // }
902           controller.$dateValue = date;
903           return controller.$dateValue;
904         });
905         // viewValue -> element
906         controller.$render = function () {
907           // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
908           element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.dateFormat));
909         };
910         // Garbage collection
911         scope.$on('$destroy', function () {
912           datepicker.destroy();
913           options = null;
914           datepicker = null;
915         });
916       }
917     };
918   }
919 ]).provider('datepickerViews', function () {
920   var defaults = this.defaults = {
921       dayFormat: 'dd',
922       daySplit: 7
923     };
924   // Split array into smaller arrays
925   function split(arr, size) {
926     var arrays = [];
927     while (arr.length > 0) {
928       arrays.push(arr.splice(0, size));
929     }
930     return arrays;
931   }
932   // Modulus operator
933   function mod(n, m) {
934     return (n % m + m) % m;
935   }
936   this.$get = [
937     '$locale',
938     '$sce',
939     'dateFilter',
940     function ($locale, $sce, dateFilter) {
941       return function (picker) {
942         var scope = picker.$scope;
943         var options = picker.$options;
944         var weekDaysMin = $locale.DATETIME_FORMATS.SHORTDAY;
945         var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));
946         var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>');
947         var startDate = picker.$date || new Date();
948         var viewDate = {
949             year: startDate.getFullYear(),
950             month: startDate.getMonth(),
951             date: startDate.getDate()
952           };
953         var timezoneOffset = startDate.getTimezoneOffset() * 60000;
954         var views = [
955             {
956               format: options.dayFormat,
957               split: 7,
958               steps: { month: 1 },
959               update: function (date, force) {
960                 if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {
961                   angular.extend(viewDate, {
962                     year: picker.$date.getFullYear(),
963                     month: picker.$date.getMonth(),
964                     date: picker.$date.getDate()
965                   });
966                   picker.$build();
967                 } else if (date.getDate() !== viewDate.date) {
968                   viewDate.date = picker.$date.getDate();
969                   picker.$updateSelected();
970                 }
971               },
972               build: function () {
973                 var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
974                 var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 7) * 86400000), firstDateOffset = firstDate.getTimezoneOffset();
975                 // Handle daylight time switch
976                 if (firstDateOffset !== firstDayOfMonthOffset)
977                   firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60000);
978                 var days = [], day;
979                 for (var i = 0; i < 42; i++) {
980                   // < 7 * 6
981                   day = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i);
982                   days.push({
983                     date: day,
984                     label: dateFilter(day, this.format),
985                     selected: picker.$date && this.isSelected(day),
986                     muted: day.getMonth() !== viewDate.month,
987                     disabled: this.isDisabled(day)
988                   });
989                 }
990                 scope.title = dateFilter(firstDayOfMonth, 'MMMM yyyy');
991                 scope.showLabels = true;
992                 scope.labels = weekDaysLabelsHtml;
993                 scope.rows = split(days, this.split);
994                 this.built = true;
995               },
996               isSelected: function (date) {
997                 return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();
998               },
999               isDisabled: function (date) {
1000                 return date.getTime() < options.minDate || date.getTime() > options.maxDate;
1001               },
1002               onKeyDown: function (evt) {
1003                 var actualTime = picker.$date.getTime();
1004                 var newDate;
1005                 if (evt.keyCode === 37)
1006                   newDate = new Date(actualTime - 1 * 86400000);
1007                 else if (evt.keyCode === 38)
1008                   newDate = new Date(actualTime - 7 * 86400000);
1009                 else if (evt.keyCode === 39)
1010                   newDate = new Date(actualTime + 1 * 86400000);
1011                 else if (evt.keyCode === 40)
1012                   newDate = new Date(actualTime + 7 * 86400000);
1013                 if (!this.isDisabled(newDate))
1014                   picker.select(newDate, true);
1015               }
1016             },
1017             {
1018               name: 'month',
1019               format: 'MMM',
1020               split: 4,
1021               steps: { year: 1 },
1022               update: function (date, force) {
1023                 if (!this.built || date.getFullYear() !== viewDate.year) {
1024                   angular.extend(viewDate, {
1025                     year: picker.$date.getFullYear(),
1026                     month: picker.$date.getMonth(),
1027                     date: picker.$date.getDate()
1028                   });
1029                   picker.$build();
1030                 } else if (date.getMonth() !== viewDate.month) {
1031                   angular.extend(viewDate, {
1032                     month: picker.$date.getMonth(),
1033                     date: picker.$date.getDate()
1034                   });
1035                   picker.$updateSelected();
1036                 }
1037               },
1038               build: function () {
1039                 var firstMonth = new Date(viewDate.year, 0, 1);
1040                 var months = [], month;
1041                 for (var i = 0; i < 12; i++) {
1042                   month = new Date(viewDate.year, i, 1);
1043                   months.push({
1044                     date: month,
1045                     label: dateFilter(month, this.format),
1046                     selected: picker.$isSelected(month),
1047                     disabled: this.isDisabled(month)
1048                   });
1049                 }
1050                 scope.title = dateFilter(month, 'yyyy');
1051                 scope.showLabels = false;
1052                 scope.rows = split(months, this.split);
1053                 this.built = true;
1054               },
1055               isSelected: function (date) {
1056                 return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();
1057               },
1058               isDisabled: function (date) {
1059                 var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);
1060                 return lastDate < options.minDate || date.getTime() > options.maxDate;
1061               },
1062               onKeyDown: function (evt) {
1063                 var actualMonth = picker.$date.getMonth();
1064                 var newDate = new Date(picker.$date);
1065                 if (evt.keyCode === 37)
1066                   newDate.setMonth(actualMonth - 1);
1067                 else if (evt.keyCode === 38)
1068                   newDate.setMonth(actualMonth - 4);
1069                 else if (evt.keyCode === 39)
1070                   newDate.setMonth(actualMonth + 1);
1071                 else if (evt.keyCode === 40)
1072                   newDate.setMonth(actualMonth + 4);
1073                 if (!this.isDisabled(newDate))
1074                   picker.select(newDate, true);
1075               }
1076             },
1077             {
1078               name: 'year',
1079               format: 'yyyy',
1080               split: 4,
1081               steps: { year: 12 },
1082               update: function (date, force) {
1083                 if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {
1084                   angular.extend(viewDate, {
1085                     year: picker.$date.getFullYear(),
1086                     month: picker.$date.getMonth(),
1087                     date: picker.$date.getDate()
1088                   });
1089                   picker.$build();
1090                 } else if (date.getFullYear() !== viewDate.year) {
1091                   angular.extend(viewDate, {
1092                     year: picker.$date.getFullYear(),
1093                     month: picker.$date.getMonth(),
1094                     date: picker.$date.getDate()
1095                   });
1096                   picker.$updateSelected();
1097                 }
1098               },
1099               build: function () {
1100                 var firstYear = viewDate.year - viewDate.year % (this.split * 3);
1101                 var years = [], year;
1102                 for (var i = 0; i < 12; i++) {
1103                   year = new Date(firstYear + i, 0, 1);
1104                   years.push({
1105                     date: year,
1106                     label: dateFilter(year, this.format),
1107                     selected: picker.$isSelected(year),
1108                     disabled: this.isDisabled(year)
1109                   });
1110                 }
1111                 scope.title = years[0].label + '-' + years[years.length - 1].label;
1112                 scope.showLabels = false;
1113                 scope.rows = split(years, this.split);
1114                 this.built = true;
1115               },
1116               isSelected: function (date) {
1117                 return picker.$date && date.getFullYear() === picker.$date.getFullYear();
1118               },
1119               isDisabled: function (date) {
1120                 var lastDate = +new Date(date.getFullYear() + 1, 0, 0);
1121                 return lastDate < options.minDate || date.getTime() > options.maxDate;
1122               },
1123               onKeyDown: function (evt) {
1124                 var actualYear = picker.$date.getFullYear(), newDate = new Date(picker.$date);
1125                 if (evt.keyCode === 37)
1126                   newDate.setYear(actualYear - 1);
1127                 else if (evt.keyCode === 38)
1128                   newDate.setYear(actualYear - 4);
1129                 else if (evt.keyCode === 39)
1130                   newDate.setYear(actualYear + 1);
1131                 else if (evt.keyCode === 40)
1132                   newDate.setYear(actualYear + 4);
1133                 if (!this.isDisabled(newDate))
1134                   picker.select(newDate, true);
1135               }
1136             }
1137           ];
1138         return {
1139           views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,
1140           viewDate: viewDate
1141         };
1142       };
1143     }
1144   ];
1145 });
1146
1147 // Source: date-parser.js
1148 angular.module('mgcrea.ngStrap.helpers.dateParser', []).provider('$dateParser', [
1149   '$localeProvider',
1150   function ($localeProvider) {
1151     var proto = Date.prototype;
1152     function isNumeric(n) {
1153       return !isNaN(parseFloat(n)) && isFinite(n);
1154     }
1155     var defaults = this.defaults = {
1156         format: 'shortDate',
1157         strict: false
1158       };
1159     this.$get = [
1160       '$locale',
1161       function ($locale) {
1162         var DateParserFactory = function (config) {
1163           var options = angular.extend({}, defaults, config);
1164           var $dateParser = {};
1165           var regExpMap = {
1166               'sss': '[0-9]{3}',
1167               'ss': '[0-5][0-9]',
1168               's': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
1169               'mm': '[0-5][0-9]',
1170               'm': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
1171               'HH': '[01][0-9]|2[0-3]',
1172               'H': options.strict ? '1?[0-9]|2[0-3]' : '[01]?[0-9]|2[0-3]',
1173               'hh': '[0][1-9]|[1][012]',
1174               'h': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
1175               'a': 'AM|PM',
1176               'EEEE': $locale.DATETIME_FORMATS.DAY.join('|'),
1177               'EEE': $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
1178               'dd': '0[1-9]|[12][0-9]|3[01]',
1179               'd': options.strict ? '[1-9]|[1-2][0-9]|3[01]' : '0?[1-9]|[1-2][0-9]|3[01]',
1180               'MMMM': $locale.DATETIME_FORMATS.MONTH.join('|'),
1181               'MMM': $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
1182               'MM': '0[1-9]|1[012]',
1183               'M': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
1184               'yyyy': '[1]{1}[0-9]{3}|[2]{1}[0-9]{3}',
1185               'yy': '[0-9]{2}',
1186               'y': options.strict ? '-?(0|[1-9][0-9]{0,3})' : '-?0*[0-9]{1,4}'
1187             };
1188           var setFnMap = {
1189               'sss': proto.setMilliseconds,
1190               'ss': proto.setSeconds,
1191               's': proto.setSeconds,
1192               'mm': proto.setMinutes,
1193               'm': proto.setMinutes,
1194               'HH': proto.setHours,
1195               'H': proto.setHours,
1196               'hh': proto.setHours,
1197               'h': proto.setHours,
1198               'dd': proto.setDate,
1199               'd': proto.setDate,
1200               'a': function (value) {
1201                 var hours = this.getHours();
1202                 return this.setHours(value.match(/pm/i) ? hours + 12 : hours);
1203               },
1204               'MMMM': function (value) {
1205                 return this.setMonth($locale.DATETIME_FORMATS.MONTH.indexOf(value));
1206               },
1207               'MMM': function (value) {
1208                 return this.setMonth($locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value));
1209               },
1210               'MM': function (value) {
1211                 return this.setMonth(1 * value - 1);
1212               },
1213               'M': function (value) {
1214                 return this.setMonth(1 * value - 1);
1215               },
1216               'yyyy': proto.setFullYear,
1217               'yy': function (value) {
1218                 return this.setFullYear(2000 + 1 * value);
1219               },
1220               'y': proto.setFullYear
1221             };
1222           var regex, setMap;
1223           $dateParser.init = function () {
1224             $dateParser.$format = $locale.DATETIME_FORMATS[options.format] || options.format;
1225             regex = regExpForFormat($dateParser.$format);
1226             setMap = setMapForFormat($dateParser.$format);
1227           };
1228           $dateParser.isValid = function (date) {
1229             if (angular.isDate(date))
1230               return !isNaN(date.getTime());
1231             return regex.test(date);
1232           };
1233           $dateParser.parse = function (value, baseDate, format) {
1234             var formatRegex = format ? regExpForFormat(format) : regex;
1235             var formatSetMap = format ? setMapForFormat(format) : setMap;
1236             if (angular.isDate(value))
1237               return value;
1238             var matches = formatRegex.exec(value);
1239             if (!matches)
1240               return false;
1241             var date = baseDate || new Date(0, 0, 1);
1242             for (var i = 0; i < matches.length - 1; i++) {
1243               formatSetMap[i] && formatSetMap[i].call(date, matches[i + 1]);
1244             }
1245             return date;
1246           };
1247           // Private functions
1248           function setMapForFormat(format) {
1249             var keys = Object.keys(setFnMap), i;
1250             var map = [], sortedMap = [];
1251             // Map to setFn
1252             var clonedFormat = format;
1253             for (i = 0; i < keys.length; i++) {
1254               if (format.split(keys[i]).length > 1) {
1255                 var index = clonedFormat.search(keys[i]);
1256                 format = format.split(keys[i]).join('');
1257                 if (setFnMap[keys[i]])
1258                   map[index] = setFnMap[keys[i]];
1259               }
1260             }
1261             // Sort result map
1262             angular.forEach(map, function (v) {
1263               sortedMap.push(v);
1264             });
1265             return sortedMap;
1266           }
1267           function escapeReservedSymbols(text) {
1268             return text.replace(/\//g, '[\\/]').replace('/-/g', '[-]').replace(/\./g, '[.]').replace(/\\s/g, '[\\s]');
1269           }
1270           function regExpForFormat(format) {
1271             var keys = Object.keys(regExpMap), i;
1272             var re = format;
1273             // Abstract replaces to avoid collisions
1274             for (i = 0; i < keys.length; i++) {
1275               re = re.split(keys[i]).join('${' + i + '}');
1276             }
1277             // Replace abstracted values
1278             for (i = 0; i < keys.length; i++) {
1279               re = re.split('${' + i + '}').join('(' + regExpMap[keys[i]] + ')');
1280             }
1281             format = escapeReservedSymbols(format);
1282             return new RegExp('^' + re + '$', ['i']);
1283           }
1284           $dateParser.init();
1285           return $dateParser;
1286         };
1287         return DateParserFactory;
1288       }
1289     ];
1290   }
1291 ]);
1292
1293 // Source: debounce.js
1294 angular.module('mgcrea.ngStrap.helpers.debounce', []).constant('debounce', function (func, wait, immediate) {
1295   var timeout, args, context, timestamp, result;
1296   return function () {
1297     context = this;
1298     args = arguments;
1299     timestamp = new Date();
1300     var later = function () {
1301       var last = new Date() - timestamp;
1302       if (last < wait) {
1303         timeout = setTimeout(later, wait - last);
1304       } else {
1305         timeout = null;
1306         if (!immediate)
1307           result = func.apply(context, args);
1308       }
1309     };
1310     var callNow = immediate && !timeout;
1311     if (!timeout) {
1312       timeout = setTimeout(later, wait);
1313     }
1314     if (callNow)
1315       result = func.apply(context, args);
1316     return result;
1317   };
1318 }).constant('throttle', function (func, wait, options) {
1319   var context, args, result;
1320   var timeout = null;
1321   var previous = 0;
1322   options || (options = {});
1323   var later = function () {
1324     previous = options.leading === false ? 0 : new Date();
1325     timeout = null;
1326     result = func.apply(context, args);
1327   };
1328   return function () {
1329     var now = new Date();
1330     if (!previous && options.leading === false)
1331       previous = now;
1332     var remaining = wait - (now - previous);
1333     context = this;
1334     args = arguments;
1335     if (remaining <= 0) {
1336       clearTimeout(timeout);
1337       timeout = null;
1338       previous = now;
1339       result = func.apply(context, args);
1340     } else if (!timeout && options.trailing !== false) {
1341       timeout = setTimeout(later, remaining);
1342     }
1343     return result;
1344   };
1345 });
1346
1347 // Source: dimensions.js
1348 angular.module('mgcrea.ngStrap.helpers.dimensions', []).factory('dimensions', [
1349   '$document',
1350   '$window',
1351   function ($document, $window) {
1352     var jqLite = angular.element;
1353     var fn = {};
1354     /**
1355      * Test the element nodeName
1356      * @param element
1357      * @param name
1358      */
1359     var nodeName = fn.nodeName = function (element, name) {
1360         return element.nodeName && element.nodeName.toLowerCase() === name.toLowerCase();
1361       };
1362     /**
1363      * Returns the element computed style
1364      * @param element
1365      * @param prop
1366      * @param extra
1367      */
1368     fn.css = function (element, prop, extra) {
1369       var value;
1370       if (element.currentStyle) {
1371         //IE
1372         value = element.currentStyle[prop];
1373       } else if (window.getComputedStyle) {
1374         value = window.getComputedStyle(element)[prop];
1375       } else {
1376         value = element.style[prop];
1377       }
1378       return extra === true ? parseFloat(value) || 0 : value;
1379     };
1380     /**
1381      * Provides read-only equivalent of jQuery's offset function:
1382      * @required-by bootstrap-tooltip, bootstrap-affix
1383      * @url http://api.jquery.com/offset/
1384      * @param element
1385      */
1386     fn.offset = function (element) {
1387       var boxRect = element.getBoundingClientRect();
1388       var docElement = element.ownerDocument;
1389       return {
1390         width: boxRect.width || element.offsetWidth,
1391         height: boxRect.height || element.offsetHeight,
1392         top: boxRect.top + (window.pageYOffset || docElement.documentElement.scrollTop) - (docElement.documentElement.clientTop || 0),
1393         left: boxRect.left + (window.pageXOffset || docElement.documentElement.scrollLeft) - (docElement.documentElement.clientLeft || 0)
1394       };
1395     };
1396     /**
1397      * Provides read-only equivalent of jQuery's position function
1398      * @required-by bootstrap-tooltip, bootstrap-affix
1399      * @url http://api.jquery.com/offset/
1400      * @param element
1401      */
1402     fn.position = function (element) {
1403       var offsetParentRect = {
1404           top: 0,
1405           left: 0
1406         }, offsetParentElement, offset;
1407       // Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
1408       if (fn.css(element, 'position') === 'fixed') {
1409         // We assume that getBoundingClientRect is available when computed position is fixed
1410         offset = element.getBoundingClientRect();
1411       } else {
1412         // Get *real* offsetParentElement
1413         offsetParentElement = offsetParent(element);
1414         offset = fn.offset(element);
1415         // Get correct offsets
1416         offset = fn.offset(element);
1417         if (!nodeName(offsetParentElement, 'html')) {
1418           offsetParentRect = fn.offset(offsetParentElement);
1419         }
1420         // Add offsetParent borders
1421         offsetParentRect.top += fn.css(offsetParentElement, 'borderTopWidth', true);
1422         offsetParentRect.left += fn.css(offsetParentElement, 'borderLeftWidth', true);
1423       }
1424       // Subtract parent offsets and element margins
1425       return {
1426         width: element.offsetWidth,
1427         height: element.offsetHeight,
1428         top: offset.top - offsetParentRect.top - fn.css(element, 'marginTop', true),
1429         left: offset.left - offsetParentRect.left - fn.css(element, 'marginLeft', true)
1430       };
1431     };
1432     /**
1433      * Returns the closest, non-statically positioned offsetParent of a given element
1434      * @required-by fn.position
1435      * @param element
1436      */
1437     var offsetParent = function offsetParentElement(element) {
1438       var docElement = element.ownerDocument;
1439       var offsetParent = element.offsetParent || docElement;
1440       if (nodeName(offsetParent, '#document'))
1441         return docElement.documentElement;
1442       while (offsetParent && !nodeName(offsetParent, 'html') && fn.css(offsetParent, 'position') === 'static') {
1443         offsetParent = offsetParent.offsetParent;
1444       }
1445       return offsetParent || docElement.documentElement;
1446     };
1447     /**
1448      * Provides equivalent of jQuery's height function
1449      * @required-by bootstrap-affix
1450      * @url http://api.jquery.com/height/
1451      * @param element
1452      * @param outer
1453      */
1454     fn.height = function (element, outer) {
1455       var value = element.offsetHeight;
1456       if (outer) {
1457         value += fn.css(element, 'marginTop', true) + fn.css(element, 'marginBottom', true);
1458       } else {
1459         value -= fn.css(element, 'paddingTop', true) + fn.css(element, 'paddingBottom', true) + fn.css(element, 'borderTopWidth', true) + fn.css(element, 'borderBottomWidth', true);
1460       }
1461       return value;
1462     };
1463     /**
1464      * Provides equivalent of jQuery's width function
1465      * @required-by bootstrap-affix
1466      * @url http://api.jquery.com/width/
1467      * @param element
1468      * @param outer
1469      */
1470     fn.width = function (element, outer) {
1471       var value = element.offsetWidth;
1472       if (outer) {
1473         value += fn.css(element, 'marginLeft', true) + fn.css(element, 'marginRight', true);
1474       } else {
1475         value -= fn.css(element, 'paddingLeft', true) + fn.css(element, 'paddingRight', true) + fn.css(element, 'borderLeftWidth', true) + fn.css(element, 'borderRightWidth', true);
1476       }
1477       return value;
1478     };
1479     return fn;
1480   }
1481 ]);
1482
1483 // Source: parse-options.js
1484 angular.module('mgcrea.ngStrap.helpers.parseOptions', []).provider('$parseOptions', function () {
1485   var defaults = this.defaults = { regexp: /^\s*(.*?)(?:\s+as\s+(.*?))?(?:\s+group\s+by\s+(.*))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+(.*?)(?:\s+track\s+by\s+(.*?))?$/ };
1486   this.$get = [
1487     '$parse',
1488     '$q',
1489     function ($parse, $q) {
1490       function ParseOptionsFactory(attr, config) {
1491         var $parseOptions = {};
1492         // Common vars
1493         var options = angular.extend({}, defaults, config);
1494         $parseOptions.$values = [];
1495         // Private vars
1496         var match, displayFn, valueName, keyName, groupByFn, valueFn, valuesFn;
1497         $parseOptions.init = function () {
1498           $parseOptions.$match = match = attr.match(options.regexp);
1499           displayFn = $parse(match[2] || match[1]), valueName = match[4] || match[6], keyName = match[5], groupByFn = $parse(match[3] || ''), valueFn = $parse(match[2] ? match[1] : valueName), valuesFn = $parse(match[7]);
1500         };
1501         $parseOptions.valuesFn = function (scope, controller) {
1502           return $q.when(valuesFn(scope, controller)).then(function (values) {
1503             $parseOptions.$values = values ? parseValues(values, scope) : {};
1504             return $parseOptions.$values;
1505           });
1506         };
1507         // Private functions
1508         function parseValues(values, scope) {
1509           return values.map(function (match, index) {
1510             var locals = {}, label, value;
1511             locals[valueName] = match;
1512             label = displayFn(scope, locals);
1513             value = valueFn(scope, locals) || index;
1514             return {
1515               label: label,
1516               value: value
1517             };
1518           });
1519         }
1520         $parseOptions.init();
1521         return $parseOptions;
1522       }
1523       return ParseOptionsFactory;
1524     }
1525   ];
1526 });
1527
1528 // Source: raf.js
1529 angular.version.minor < 3 && angular.version.dot < 14 && angular.module('ng').factory('$$rAF', [
1530   '$window',
1531   '$timeout',
1532   function ($window, $timeout) {
1533     var requestAnimationFrame = $window.requestAnimationFrame || $window.webkitRequestAnimationFrame || $window.mozRequestAnimationFrame;
1534     var cancelAnimationFrame = $window.cancelAnimationFrame || $window.webkitCancelAnimationFrame || $window.mozCancelAnimationFrame || $window.webkitCancelRequestAnimationFrame;
1535     var rafSupported = !!requestAnimationFrame;
1536     var raf = rafSupported ? function (fn) {
1537         var id = requestAnimationFrame(fn);
1538         return function () {
1539           cancelAnimationFrame(id);
1540         };
1541       } : function (fn) {
1542         var timer = $timeout(fn, 16.66, false);
1543         // 1000 / 60 = 16.666
1544         return function () {
1545           $timeout.cancel(timer);
1546         };
1547       };
1548     raf.supported = rafSupported;
1549     return raf;
1550   }
1551 ]);  // .factory('$$animateReflow', function($$rAF, $document) {
1552      //   var bodyEl = $document[0].body;
1553      //   return function(fn) {
1554      //     //the returned function acts as the cancellation function
1555      //     return $$rAF(function() {
1556      //       //the line below will force the browser to perform a repaint
1557      //       //so that all the animated elements within the animation frame
1558      //       //will be properly updated and drawn on screen. This is
1559      //       //required to perform multi-class CSS based animations with
1560      //       //Firefox. DO NOT REMOVE THIS LINE.
1561      //       var a = bodyEl.offsetWidth + 1;
1562      //       fn();
1563      //     });
1564      //   };
1565      // });
1566
1567 // Source: dropdown.js
1568 angular.module('mgcrea.ngStrap.dropdown', ['mgcrea.ngStrap.tooltip']).provider('$dropdown', function () {
1569   var defaults = this.defaults = {
1570       animation: 'am-fade',
1571       prefixClass: 'dropdown',
1572       placement: 'bottom-left',
1573       template: 'dropdown/dropdown.tpl.html',
1574       trigger: 'click',
1575       container: false,
1576       keyboard: true,
1577       html: false,
1578       delay: 0
1579     };
1580   this.$get = [
1581     '$window',
1582     '$rootScope',
1583     '$tooltip',
1584     function ($window, $rootScope, $tooltip) {
1585       var bodyEl = angular.element($window.document.body);
1586       var matchesSelector = Element.prototype.matchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector;
1587       function DropdownFactory(element, config) {
1588         var $dropdown = {};
1589         // Common vars
1590         var options = angular.extend({}, defaults, config);
1591         var scope = $dropdown.$scope = options.scope && options.scope.$new() || $rootScope.$new();
1592         $dropdown = $tooltip(element, options);
1593         // Protected methods
1594         $dropdown.$onKeyDown = function (evt) {
1595           if (!/(38|40)/.test(evt.keyCode))
1596             return;
1597           evt.preventDefault();
1598           evt.stopPropagation();
1599           // Retrieve focused index
1600           var items = angular.element($dropdown.$element[0].querySelectorAll('li:not(.divider) a'));
1601           if (!items.length)
1602             return;
1603           var index;
1604           angular.forEach(items, function (el, i) {
1605             if (matchesSelector && matchesSelector.call(el, ':focus'))
1606               index = i;
1607           });
1608           // Navigate with keyboard
1609           if (evt.keyCode === 38 && index > 0)
1610             index--;
1611           else if (evt.keyCode === 40 && index < items.length - 1)
1612             index++;
1613           else if (angular.isUndefined(index))
1614             index = 0;
1615           items.eq(index)[0].focus();
1616         };
1617         // Overrides
1618         var show = $dropdown.show;
1619         $dropdown.show = function () {
1620           show();
1621           setTimeout(function () {
1622             options.keyboard && $dropdown.$element.on('keydown', $dropdown.$onKeyDown);
1623             bodyEl.on('click', onBodyClick);
1624           });
1625         };
1626         var hide = $dropdown.hide;
1627         $dropdown.hide = function () {
1628           options.keyboard && $dropdown.$element.off('keydown', $dropdown.$onKeyDown);
1629           bodyEl.off('click', onBodyClick);
1630           hide();
1631         };
1632         // Private functions
1633         function onBodyClick(evt) {
1634           if (evt.target === element[0])
1635             return;
1636           return evt.target !== element[0] && $dropdown.hide();
1637         }
1638         return $dropdown;
1639       }
1640       return DropdownFactory;
1641     }
1642   ];
1643 }).directive('bsDropdown', [
1644   '$window',
1645   '$location',
1646   '$sce',
1647   '$dropdown',
1648   function ($window, $location, $sce, $dropdown) {
1649     return {
1650       restrict: 'EAC',
1651       scope: true,
1652       link: function postLink(scope, element, attr, transclusion) {
1653         // Directive options
1654         var options = { scope: scope };
1655         angular.forEach([
1656           'placement',
1657           'container',
1658           'delay',
1659           'trigger',
1660           'keyboard',
1661           'html',
1662           'animation',
1663           'template'
1664         ], function (key) {
1665           if (angular.isDefined(attr[key]))
1666             options[key] = attr[key];
1667         });
1668         // Support scope as an object
1669         attr.bsDropdown && scope.$watch(attr.bsDropdown, function (newValue, oldValue) {
1670           scope.content = newValue;
1671         }, true);
1672         // Initialize dropdown
1673         var dropdown = $dropdown(element, options);
1674         // Garbage collection
1675         scope.$on('$destroy', function () {
1676           dropdown.destroy();
1677           options = null;
1678           dropdown = null;
1679         });
1680       }
1681     };
1682   }
1683 ]);
1684
1685 // Source: modal.js
1686 angular.module('mgcrea.ngStrap.modal', ['mgcrea.ngStrap.helpers.dimensions']).provider('$modal', function () {
1687   var defaults = this.defaults = {
1688       animation: 'am-fade',
1689       backdropAnimation: 'am-fade',
1690       prefixClass: 'modal',
1691       prefixEvent: 'modal',
1692       placement: 'top',
1693       template: 'modal/modal.tpl.html',
1694       contentTemplate: false,
1695       container: false,
1696       element: null,
1697       backdrop: true,
1698       keyboard: true,
1699       html: false,
1700       show: true
1701     };
1702   this.$get = [
1703     '$window',
1704     '$rootScope',
1705     '$compile',
1706     '$q',
1707     '$templateCache',
1708     '$http',
1709     '$animate',
1710     '$timeout',
1711     '$sce',
1712     'dimensions',
1713     function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, $timeout, $sce, dimensions) {
1714       var forEach = angular.forEach;
1715       var trim = String.prototype.trim;
1716       var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
1717       var bodyElement = angular.element($window.document.body);
1718       var htmlReplaceRegExp = /ng-bind="/gi;
1719       function ModalFactory(config) {
1720         var $modal = {};
1721         // Common vars
1722         var options = $modal.$options = angular.extend({}, defaults, config);
1723         $modal.$promise = fetchTemplate(options.template);
1724         var scope = $modal.$scope = options.scope && options.scope.$new() || $rootScope.$new();
1725         if (!options.element && !options.container) {
1726           options.container = 'body';
1727         }
1728         // Support scope as string options
1729         forEach([
1730           'title',
1731           'content'
1732         ], function (key) {
1733           if (options[key])
1734             scope[key] = $sce.trustAsHtml(options[key]);
1735         });
1736         // Provide scope helpers
1737         scope.$hide = function () {
1738           scope.$$postDigest(function () {
1739             $modal.hide();
1740           });
1741         };
1742         scope.$show = function () {
1743           scope.$$postDigest(function () {
1744             $modal.show();
1745           });
1746         };
1747         scope.$toggle = function () {
1748           scope.$$postDigest(function () {
1749             $modal.toggle();
1750           });
1751         };
1752         // Support contentTemplate option
1753         if (options.contentTemplate) {
1754           $modal.$promise = $modal.$promise.then(function (template) {
1755             var templateEl = angular.element(template);
1756             return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
1757               var contentEl = findElement('[ng-bind="content"]', templateEl[0]).removeAttr('ng-bind').html(contentTemplate);
1758               // Drop the default footer as you probably don't want it if you use a custom contentTemplate
1759               if (!config.template)
1760                 contentEl.next().remove();
1761               return templateEl[0].outerHTML;
1762             });
1763           });
1764         }
1765         // Fetch, compile then initialize modal
1766         var modalLinker, modalElement;
1767         var backdropElement = angular.element('<div class="' + options.prefixClass + '-backdrop"/>');
1768         $modal.$promise.then(function (template) {
1769           if (angular.isObject(template))
1770             template = template.data;
1771           if (options.html)
1772             template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
1773           template = trim.apply(template);
1774           modalLinker = $compile(template);
1775           $modal.init();
1776         });
1777         $modal.init = function () {
1778           // Options: show
1779           if (options.show) {
1780             scope.$$postDigest(function () {
1781               $modal.show();
1782             });
1783           }
1784         };
1785         $modal.destroy = function () {
1786           // Remove element
1787           if (modalElement) {
1788             modalElement.remove();
1789             modalElement = null;
1790           }
1791           if (backdropElement) {
1792             backdropElement.remove();
1793             backdropElement = null;
1794           }
1795           // Destroy scope
1796           scope.$destroy();
1797         };
1798         $modal.show = function () {
1799           scope.$emit(options.prefixEvent + '.show.before', $modal);
1800           var parent = options.container ? findElement(options.container) : null;
1801           var after = options.container ? null : options.element;
1802           // Fetch a cloned element linked from template
1803           modalElement = $modal.$element = modalLinker(scope, function (clonedElement, scope) {
1804           });
1805           // Set the initial positioning.
1806           modalElement.css({ display: 'block' }).addClass(options.placement);
1807           // Options: animation
1808           if (options.animation) {
1809             if (options.backdrop) {
1810               backdropElement.addClass(options.backdropAnimation);
1811             }
1812             modalElement.addClass(options.animation);
1813           }
1814           if (options.backdrop) {
1815             $animate.enter(backdropElement, bodyElement, null, function () {
1816             });
1817           }
1818           $animate.enter(modalElement, parent, after, function () {
1819             scope.$emit(options.prefixEvent + '.show', $modal);
1820           });
1821           scope.$isShown = true;
1822           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
1823           // Focus once the enter-animation has started
1824           // Weird PhantomJS bug hack
1825           var el = modalElement[0];
1826           requestAnimationFrame(function () {
1827             el.focus();
1828           });
1829           bodyElement.addClass(options.prefixClass + '-open');
1830           if (options.animation) {
1831             bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
1832           }
1833           // Bind events
1834           if (options.backdrop) {
1835             modalElement.on('click', hideOnBackdropClick);
1836             backdropElement.on('click', hideOnBackdropClick);
1837           }
1838           if (options.keyboard) {
1839             modalElement.on('keyup', $modal.$onKeyUp);
1840           }
1841         };
1842         $modal.hide = function () {
1843           scope.$emit(options.prefixEvent + '.hide.before', $modal);
1844           $animate.leave(modalElement, function () {
1845             scope.$emit(options.prefixEvent + '.hide', $modal);
1846             bodyElement.removeClass(options.prefixClass + '-open');
1847             if (options.animation) {
1848               bodyElement.addClass(options.prefixClass + '-with-' + options.animation);
1849             }
1850           });
1851           if (options.backdrop) {
1852             $animate.leave(backdropElement, function () {
1853             });
1854           }
1855           scope.$isShown = false;
1856           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
1857           // Unbind events
1858           if (options.backdrop) {
1859             modalElement.off('click', hideOnBackdropClick);
1860             backdropElement.off('click', hideOnBackdropClick);
1861           }
1862           if (options.keyboard) {
1863             modalElement.off('keyup', $modal.$onKeyUp);
1864           }
1865         };
1866         $modal.toggle = function () {
1867           scope.$isShown ? $modal.hide() : $modal.show();
1868         };
1869         $modal.focus = function () {
1870           modalElement[0].focus();
1871         };
1872         // Protected methods
1873         $modal.$onKeyUp = function (evt) {
1874           evt.which === 27 && $modal.hide();
1875         };
1876         // Private methods
1877         function hideOnBackdropClick(evt) {
1878           if (evt.target !== evt.currentTarget)
1879             return;
1880           options.backdrop === 'static' ? $modal.focus() : $modal.hide();
1881         }
1882         return $modal;
1883       }
1884       // Helper functions
1885       function findElement(query, element) {
1886         return angular.element((element || document).querySelectorAll(query));
1887       }
1888       function fetchTemplate(template) {
1889         return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
1890           if (angular.isObject(res)) {
1891             $templateCache.put(template, res.data);
1892             return res.data;
1893           }
1894           return res;
1895         });
1896       }
1897       return ModalFactory;
1898     }
1899   ];
1900 }).directive('bsModal', [
1901   '$window',
1902   '$location',
1903   '$sce',
1904   '$modal',
1905   function ($window, $location, $sce, $modal) {
1906     return {
1907       restrict: 'EAC',
1908       scope: true,
1909       link: function postLink(scope, element, attr, transclusion) {
1910         // Directive options
1911         var options = {
1912             scope: scope,
1913             element: element,
1914             show: false
1915           };
1916         angular.forEach([
1917           'template',
1918           'contentTemplate',
1919           'placement',
1920           'backdrop',
1921           'keyboard',
1922           'html',
1923           'container',
1924           'animation'
1925         ], function (key) {
1926           if (angular.isDefined(attr[key]))
1927             options[key] = attr[key];
1928         });
1929         // Support scope as data-attrs
1930         angular.forEach([
1931           'title',
1932           'content'
1933         ], function (key) {
1934           attr[key] && attr.$observe(key, function (newValue, oldValue) {
1935             scope[key] = $sce.trustAsHtml(newValue);
1936           });
1937         });
1938         // Support scope as an object
1939         attr.bsModal && scope.$watch(attr.bsModal, function (newValue, oldValue) {
1940           if (angular.isObject(newValue)) {
1941             angular.extend(scope, newValue);
1942           } else {
1943             scope.content = newValue;
1944           }
1945         }, true);
1946         // Initialize modal
1947         var modal = $modal(options);
1948         // Trigger
1949         element.on(attr.trigger || 'click', modal.toggle);
1950         // Garbage collection
1951         scope.$on('$destroy', function () {
1952           modal.destroy();
1953           options = null;
1954           modal = null;
1955         });
1956       }
1957     };
1958   }
1959 ]);
1960
1961 // Source: navbar.js
1962 angular.module('mgcrea.ngStrap.navbar', []).provider('$navbar', function () {
1963   var defaults = this.defaults = {
1964       activeClass: 'active',
1965       routeAttr: 'data-match-route',
1966       strict: false
1967     };
1968   this.$get = function () {
1969     return { defaults: defaults };
1970   };
1971 }).directive('bsNavbar', [
1972   '$window',
1973   '$location',
1974   '$navbar',
1975   function ($window, $location, $navbar) {
1976     var defaults = $navbar.defaults;
1977     return {
1978       restrict: 'A',
1979       link: function postLink(scope, element, attr, controller) {
1980         // Directive options
1981         var options = angular.copy(defaults);
1982         angular.forEach(Object.keys(defaults), function (key) {
1983           if (angular.isDefined(attr[key]))
1984             options[key] = attr[key];
1985         });
1986         // Watch for the $location
1987         scope.$watch(function () {
1988           return $location.path();
1989         }, function (newValue, oldValue) {
1990           var liElements = element[0].querySelectorAll('li[' + options.routeAttr + ']');
1991           angular.forEach(liElements, function (li) {
1992             var liElement = angular.element(li);
1993             var pattern = liElement.attr(options.routeAttr).replace('/', '\\/');
1994             if (options.strict) {
1995               pattern = '^' + pattern + '$';
1996             }
1997             var regexp = new RegExp(pattern, ['i']);
1998             if (regexp.test(newValue)) {
1999               liElement.addClass(options.activeClass);
2000             } else {
2001               liElement.removeClass(options.activeClass);
2002             }
2003           });
2004         });
2005       }
2006     };
2007   }
2008 ]);
2009
2010 // Source: scrollspy.js
2011 angular.module('mgcrea.ngStrap.scrollspy', [
2012   'mgcrea.ngStrap.helpers.debounce',
2013   'mgcrea.ngStrap.helpers.dimensions'
2014 ]).provider('$scrollspy', function () {
2015   // Pool of registered spies
2016   var spies = this.$$spies = {};
2017   var defaults = this.defaults = {
2018       debounce: 150,
2019       throttle: 100,
2020       offset: 100
2021     };
2022   this.$get = [
2023     '$window',
2024     '$document',
2025     '$rootScope',
2026     'dimensions',
2027     'debounce',
2028     'throttle',
2029     function ($window, $document, $rootScope, dimensions, debounce, throttle) {
2030       var windowEl = angular.element($window);
2031       var docEl = angular.element($document.prop('documentElement'));
2032       var bodyEl = angular.element($window.document.body);
2033       // Helper functions
2034       function nodeName(element, name) {
2035         return element[0].nodeName && element[0].nodeName.toLowerCase() === name.toLowerCase();
2036       }
2037       function ScrollSpyFactory(config) {
2038         // Common vars
2039         var options = angular.extend({}, defaults, config);
2040         if (!options.element)
2041           options.element = bodyEl;
2042         var isWindowSpy = nodeName(options.element, 'body');
2043         var scrollEl = isWindowSpy ? windowEl : options.element;
2044         var scrollId = isWindowSpy ? 'window' : options.id;
2045         // Use existing spy
2046         if (spies[scrollId]) {
2047           spies[scrollId].$$count++;
2048           return spies[scrollId];
2049         }
2050         var $scrollspy = {};
2051         // Private vars
2052         var unbindViewContentLoaded, unbindIncludeContentLoaded;
2053         var trackedElements = $scrollspy.$trackedElements = [];
2054         var sortedElements = [];
2055         var activeTarget;
2056         var debouncedCheckPosition;
2057         var throttledCheckPosition;
2058         var debouncedCheckOffsets;
2059         var viewportHeight;
2060         var scrollTop;
2061         $scrollspy.init = function () {
2062           // Setup internal ref counter
2063           this.$$count = 1;
2064           // Bind events
2065           debouncedCheckPosition = debounce(this.checkPosition, options.debounce);
2066           throttledCheckPosition = throttle(this.checkPosition, options.throttle);
2067           scrollEl.on('click', this.checkPositionWithEventLoop);
2068           windowEl.on('resize', debouncedCheckPosition);
2069           scrollEl.on('scroll', throttledCheckPosition);
2070           debouncedCheckOffsets = debounce(this.checkOffsets, options.debounce);
2071           unbindViewContentLoaded = $rootScope.$on('$viewContentLoaded', debouncedCheckOffsets);
2072           unbindIncludeContentLoaded = $rootScope.$on('$includeContentLoaded', debouncedCheckOffsets);
2073           debouncedCheckOffsets();
2074           // Register spy for reuse
2075           if (scrollId) {
2076             spies[scrollId] = $scrollspy;
2077           }
2078         };
2079         $scrollspy.destroy = function () {
2080           // Check internal ref counter
2081           this.$$count--;
2082           if (this.$$count > 0) {
2083             return;
2084           }
2085           // Unbind events
2086           scrollEl.off('click', this.checkPositionWithEventLoop);
2087           windowEl.off('resize', debouncedCheckPosition);
2088           scrollEl.off('scroll', debouncedCheckPosition);
2089           unbindViewContentLoaded();
2090           unbindIncludeContentLoaded();
2091           if (scrollId) {
2092             delete spies[scrollId];
2093           }
2094         };
2095         $scrollspy.checkPosition = function () {
2096           // Not ready yet
2097           if (!sortedElements.length)
2098             return;
2099           // Calculate the scroll position
2100           scrollTop = (isWindowSpy ? $window.pageYOffset : scrollEl.prop('scrollTop')) || 0;
2101           // Calculate the viewport height for use by the components
2102           viewportHeight = Math.max($window.innerHeight, docEl.prop('clientHeight'));
2103           // Activate first element if scroll is smaller
2104           if (scrollTop < sortedElements[0].offsetTop && activeTarget !== sortedElements[0].target) {
2105             return $scrollspy.$activateElement(sortedElements[0]);
2106           }
2107           // Activate proper element
2108           for (var i = sortedElements.length; i--;) {
2109             if (angular.isUndefined(sortedElements[i].offsetTop) || sortedElements[i].offsetTop === null)
2110               continue;
2111             if (activeTarget === sortedElements[i].target)
2112               continue;
2113             if (scrollTop < sortedElements[i].offsetTop)
2114               continue;
2115             if (sortedElements[i + 1] && scrollTop > sortedElements[i + 1].offsetTop)
2116               continue;
2117             return $scrollspy.$activateElement(sortedElements[i]);
2118           }
2119         };
2120         $scrollspy.checkPositionWithEventLoop = function () {
2121           setTimeout(this.checkPosition, 1);
2122         };
2123         // Protected methods
2124         $scrollspy.$activateElement = function (element) {
2125           if (activeTarget) {
2126             var activeElement = $scrollspy.$getTrackedElement(activeTarget);
2127             if (activeElement) {
2128               activeElement.source.removeClass('active');
2129               if (nodeName(activeElement.source, 'li') && nodeName(activeElement.source.parent().parent(), 'li')) {
2130                 activeElement.source.parent().parent().removeClass('active');
2131               }
2132             }
2133           }
2134           activeTarget = element.target;
2135           element.source.addClass('active');
2136           if (nodeName(element.source, 'li') && nodeName(element.source.parent().parent(), 'li')) {
2137             element.source.parent().parent().addClass('active');
2138           }
2139         };
2140         $scrollspy.$getTrackedElement = function (target) {
2141           return trackedElements.filter(function (obj) {
2142             return obj.target === target;
2143           })[0];
2144         };
2145         // Track offsets behavior
2146         $scrollspy.checkOffsets = function () {
2147           angular.forEach(trackedElements, function (trackedElement) {
2148             var targetElement = document.querySelector(trackedElement.target);
2149             trackedElement.offsetTop = targetElement ? dimensions.offset(targetElement).top : null;
2150             if (options.offset && trackedElement.offsetTop !== null)
2151               trackedElement.offsetTop -= options.offset * 1;
2152           });
2153           sortedElements = trackedElements.filter(function (el) {
2154             return el.offsetTop !== null;
2155           }).sort(function (a, b) {
2156             return a.offsetTop - b.offsetTop;
2157           });
2158           debouncedCheckPosition();
2159         };
2160         $scrollspy.trackElement = function (target, source) {
2161           trackedElements.push({
2162             target: target,
2163             source: source
2164           });
2165         };
2166         $scrollspy.untrackElement = function (target, source) {
2167           var toDelete;
2168           for (var i = trackedElements.length; i--;) {
2169             if (trackedElements[i].target === target && trackedElements[i].source === source) {
2170               toDelete = i;
2171               break;
2172             }
2173           }
2174           trackedElements = trackedElements.splice(toDelete, 1);
2175         };
2176         $scrollspy.activate = function (i) {
2177           trackedElements[i].addClass('active');
2178         };
2179         // Initialize plugin
2180         $scrollspy.init();
2181         return $scrollspy;
2182       }
2183       return ScrollSpyFactory;
2184     }
2185   ];
2186 }).directive('bsScrollspy', [
2187   '$rootScope',
2188   'debounce',
2189   'dimensions',
2190   '$scrollspy',
2191   function ($rootScope, debounce, dimensions, $scrollspy) {
2192     return {
2193       restrict: 'EAC',
2194       link: function postLink(scope, element, attr) {
2195         var options = { scope: scope };
2196         angular.forEach([
2197           'offset',
2198           'target'
2199         ], function (key) {
2200           if (angular.isDefined(attr[key]))
2201             options[key] = attr[key];
2202         });
2203         var scrollspy = $scrollspy(options);
2204         scrollspy.trackElement(options.target, element);
2205         scope.$on('$destroy', function () {
2206           scrollspy.untrackElement(options.target, element);
2207           scrollspy.destroy();
2208           options = null;
2209           scrollspy = null;
2210         });
2211       }
2212     };
2213   }
2214 ]).directive('bsScrollspyList', [
2215   '$rootScope',
2216   'debounce',
2217   'dimensions',
2218   '$scrollspy',
2219   function ($rootScope, debounce, dimensions, $scrollspy) {
2220     return {
2221       restrict: 'A',
2222       compile: function postLink(element, attr) {
2223         var children = element[0].querySelectorAll('li > a[href]');
2224         angular.forEach(children, function (child) {
2225           var childEl = angular.element(child);
2226           childEl.parent().attr('bs-scrollspy', '').attr('data-target', childEl.attr('href'));
2227         });
2228       }
2229     };
2230   }
2231 ]);
2232
2233 // Source: popover.js
2234 angular.module('mgcrea.ngStrap.popover', ['mgcrea.ngStrap.tooltip']).provider('$popover', function () {
2235   var defaults = this.defaults = {
2236       animation: 'am-fade',
2237       container: false,
2238       target: false,
2239       placement: 'right',
2240       template: 'popover/popover.tpl.html',
2241       contentTemplate: false,
2242       trigger: 'click',
2243       keyboard: true,
2244       html: false,
2245       title: '',
2246       content: '',
2247       delay: 0
2248     };
2249   this.$get = [
2250     '$tooltip',
2251     function ($tooltip) {
2252       function PopoverFactory(element, config) {
2253         // Common vars
2254         var options = angular.extend({}, defaults, config);
2255         var $popover = $tooltip(element, options);
2256         // Support scope as string options [/*title, */content]
2257         if (options.content) {
2258           $popover.$scope.content = options.content;
2259         }
2260         return $popover;
2261       }
2262       return PopoverFactory;
2263     }
2264   ];
2265 }).directive('bsPopover', [
2266   '$window',
2267   '$location',
2268   '$sce',
2269   '$popover',
2270   function ($window, $location, $sce, $popover) {
2271     var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2272     return {
2273       restrict: 'EAC',
2274       scope: true,
2275       link: function postLink(scope, element, attr) {
2276         // Directive options
2277         var options = { scope: scope };
2278         angular.forEach([
2279           'template',
2280           'contentTemplate',
2281           'placement',
2282           'container',
2283           'target',
2284           'delay',
2285           'trigger',
2286           'keyboard',
2287           'html',
2288           'animation'
2289         ], function (key) {
2290           if (angular.isDefined(attr[key]))
2291             options[key] = attr[key];
2292         });
2293         // Support scope as data-attrs
2294         angular.forEach([
2295           'title',
2296           'content'
2297         ], function (key) {
2298           attr[key] && attr.$observe(key, function (newValue, oldValue) {
2299             scope[key] = $sce.trustAsHtml(newValue);
2300             angular.isDefined(oldValue) && requestAnimationFrame(function () {
2301               popover && popover.$applyPlacement();
2302             });
2303           });
2304         });
2305         // Support scope as an object
2306         attr.bsPopover && scope.$watch(attr.bsPopover, function (newValue, oldValue) {
2307           if (angular.isObject(newValue)) {
2308             angular.extend(scope, newValue);
2309           } else {
2310             scope.content = newValue;
2311           }
2312           angular.isDefined(oldValue) && requestAnimationFrame(function () {
2313             popover && popover.$applyPlacement();
2314           });
2315         }, true);
2316         // Initialize popover
2317         var popover = $popover(element, options);
2318         // Garbage collection
2319         scope.$on('$destroy', function () {
2320           popover.destroy();
2321           options = null;
2322           popover = null;
2323         });
2324       }
2325     };
2326   }
2327 ]);
2328
2329 // Source: select.js
2330 angular.module('mgcrea.ngStrap.select', [
2331   'mgcrea.ngStrap.tooltip',
2332   'mgcrea.ngStrap.helpers.parseOptions'
2333 ]).provider('$select', function () {
2334   var defaults = this.defaults = {
2335       animation: 'am-fade',
2336       prefixClass: 'select',
2337       placement: 'bottom-left',
2338       template: 'select/select.tpl.html',
2339       trigger: 'focus',
2340       container: false,
2341       keyboard: true,
2342       html: false,
2343       delay: 0,
2344       multiple: false,
2345       sort: true,
2346       caretHtml: '&nbsp;<span class="caret"></span>',
2347       placeholder: 'Choose among the following...',
2348       maxLength: 3,
2349       maxLengthHtml: 'selected',
2350       iconCheckmark: 'glyphicon glyphicon-ok'
2351     };
2352   this.$get = [
2353     '$window',
2354     '$document',
2355     '$rootScope',
2356     '$tooltip',
2357     function ($window, $document, $rootScope, $tooltip) {
2358       var bodyEl = angular.element($window.document.body);
2359       var isTouch = 'createTouch' in $window.document;
2360       function SelectFactory(element, controller, config) {
2361         var $select = {};
2362         // Common vars
2363         var options = angular.extend({}, defaults, config);
2364         $select = $tooltip(element, options);
2365         var scope = $select.$scope;
2366         scope.$matches = [];
2367         scope.$activeIndex = 0;
2368         scope.$isMultiple = options.multiple;
2369         scope.$iconCheckmark = options.iconCheckmark;
2370         scope.$activate = function (index) {
2371           scope.$$postDigest(function () {
2372             $select.activate(index);
2373           });
2374         };
2375         scope.$select = function (index, evt) {
2376           scope.$$postDigest(function () {
2377             $select.select(index);
2378           });
2379         };
2380         scope.$isVisible = function () {
2381           return $select.$isVisible();
2382         };
2383         scope.$isActive = function (index) {
2384           return $select.$isActive(index);
2385         };
2386         // Public methods
2387         $select.update = function (matches) {
2388           scope.$matches = matches;
2389           $select.$updateActiveIndex();
2390         };
2391         $select.activate = function (index) {
2392           if (options.multiple) {
2393             scope.$activeIndex.sort();
2394             $select.$isActive(index) ? scope.$activeIndex.splice(scope.$activeIndex.indexOf(index), 1) : scope.$activeIndex.push(index);
2395             if (options.sort)
2396               scope.$activeIndex.sort();
2397           } else {
2398             scope.$activeIndex = index;
2399           }
2400           return scope.$activeIndex;
2401         };
2402         $select.select = function (index) {
2403           var value = scope.$matches[index].value;
2404           scope.$apply(function () {
2405             $select.activate(index);
2406             if (options.multiple) {
2407               controller.$setViewValue(scope.$activeIndex.map(function (index) {
2408                 return scope.$matches[index].value;
2409               }));
2410             } else {
2411               controller.$setViewValue(value);
2412               // Hide if single select
2413               $select.hide();
2414             }
2415           });
2416           // Emit event
2417           scope.$emit('$select.select', value, index);
2418         };
2419         // Protected methods
2420         $select.$updateActiveIndex = function () {
2421           if (controller.$modelValue && scope.$matches.length) {
2422             if (options.multiple && angular.isArray(controller.$modelValue)) {
2423               scope.$activeIndex = controller.$modelValue.map(function (value) {
2424                 return $select.$getIndex(value);
2425               });
2426             } else {
2427               scope.$activeIndex = $select.$getIndex(controller.$modelValue);
2428             }
2429           } else if (scope.$activeIndex >= scope.$matches.length) {
2430             scope.$activeIndex = options.multiple ? [] : 0;
2431           }
2432         };
2433         $select.$isVisible = function () {
2434           if (!options.minLength || !controller) {
2435             return scope.$matches.length;
2436           }
2437           // minLength support
2438           return scope.$matches.length && controller.$viewValue.length >= options.minLength;
2439         };
2440         $select.$isActive = function (index) {
2441           if (options.multiple) {
2442             return scope.$activeIndex.indexOf(index) !== -1;
2443           } else {
2444             return scope.$activeIndex === index;
2445           }
2446         };
2447         $select.$getIndex = function (value) {
2448           var l = scope.$matches.length, i = l;
2449           if (!l)
2450             return;
2451           for (i = l; i--;) {
2452             if (scope.$matches[i].value === value)
2453               break;
2454           }
2455           if (i < 0)
2456             return;
2457           return i;
2458         };
2459         $select.$onMouseDown = function (evt) {
2460           // Prevent blur on mousedown on .dropdown-menu
2461           evt.preventDefault();
2462           evt.stopPropagation();
2463           // Emulate click for mobile devices
2464           if (isTouch) {
2465             var targetEl = angular.element(evt.target);
2466             targetEl.triggerHandler('click');
2467           }
2468         };
2469         $select.$onKeyDown = function (evt) {
2470           if (!/(9|13|38|40)/.test(evt.keyCode))
2471             return;
2472           evt.preventDefault();
2473           evt.stopPropagation();
2474           // Select with enter
2475           if (!options.multiple && (evt.keyCode === 13 || evt.keyCode === 9)) {
2476             return $select.select(scope.$activeIndex);
2477           }
2478           // Navigate with keyboard
2479           if (evt.keyCode === 38 && scope.$activeIndex > 0)
2480             scope.$activeIndex--;
2481           else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1)
2482             scope.$activeIndex++;
2483           else if (angular.isUndefined(scope.$activeIndex))
2484             scope.$activeIndex = 0;
2485           scope.$digest();
2486         };
2487         // Overrides
2488         var _show = $select.show;
2489         $select.show = function () {
2490           _show();
2491           if (options.multiple) {
2492             $select.$element.addClass('select-multiple');
2493           }
2494           setTimeout(function () {
2495             $select.$element.on(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
2496             if (options.keyboard) {
2497               element.on('keydown', $select.$onKeyDown);
2498             }
2499           });
2500         };
2501         var _hide = $select.hide;
2502         $select.hide = function () {
2503           $select.$element.off(isTouch ? 'touchstart' : 'mousedown', $select.$onMouseDown);
2504           if (options.keyboard) {
2505             element.off('keydown', $select.$onKeyDown);
2506           }
2507           _hide(true);
2508         };
2509         return $select;
2510       }
2511       SelectFactory.defaults = defaults;
2512       return SelectFactory;
2513     }
2514   ];
2515 }).directive('bsSelect', [
2516   '$window',
2517   '$parse',
2518   '$q',
2519   '$select',
2520   '$parseOptions',
2521   function ($window, $parse, $q, $select, $parseOptions) {
2522     var defaults = $select.defaults;
2523     return {
2524       restrict: 'EAC',
2525       require: 'ngModel',
2526       link: function postLink(scope, element, attr, controller) {
2527         // Directive options
2528         var options = { scope: scope };
2529         angular.forEach([
2530           'placement',
2531           'container',
2532           'delay',
2533           'trigger',
2534           'keyboard',
2535           'html',
2536           'animation',
2537           'template',
2538           'placeholder',
2539           'multiple',
2540           'maxLength',
2541           'maxLengthHtml'
2542         ], function (key) {
2543           if (angular.isDefined(attr[key]))
2544             options[key] = attr[key];
2545         });
2546         // Add support for select markup
2547         if (element[0].nodeName.toLowerCase() === 'select') {
2548           var inputEl = element;
2549           inputEl.css('display', 'none');
2550           element = angular.element('<button type="button" class="btn btn-default"></button>');
2551           inputEl.after(element);
2552         }
2553         // Build proper ngOptions
2554         var parsedOptions = $parseOptions(attr.ngOptions);
2555         // Initialize select
2556         var select = $select(element, controller, options);
2557         // Watch ngOptions values before filtering for changes
2558         var watchedOptions = parsedOptions.$match[7].replace(/\|.+/, '').trim();
2559         scope.$watch(watchedOptions, function (newValue, oldValue) {
2560           // console.warn('scope.$watch(%s)', watchedOptions, newValue, oldValue);
2561           parsedOptions.valuesFn(scope, controller).then(function (values) {
2562             select.update(values);
2563             controller.$render();
2564           });
2565         }, true);
2566         // Watch model for changes
2567         scope.$watch(attr.ngModel, function (newValue, oldValue) {
2568           // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue);
2569           select.$updateActiveIndex();
2570           controller.$render();
2571         }, true);
2572         // Model rendering in view
2573         controller.$render = function () {
2574           // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
2575           var selected, index;
2576           if (options.multiple && angular.isArray(controller.$modelValue)) {
2577             selected = controller.$modelValue.map(function (value) {
2578               index = select.$getIndex(value);
2579               return angular.isDefined(index) ? select.$scope.$matches[index].label : false;
2580             }).filter(angular.isDefined);
2581             if (selected.length > (options.maxLength || defaults.maxLength)) {
2582               selected = selected.length + ' ' + (options.maxLengthHtml || defaults.maxLengthHtml);
2583             } else {
2584               selected = selected.join(', ');
2585             }
2586           } else {
2587             index = select.$getIndex(controller.$modelValue);
2588             selected = angular.isDefined(index) ? select.$scope.$matches[index].label : false;
2589           }
2590           element.html((selected ? selected : attr.placeholder || defaults.placeholder) + defaults.caretHtml);
2591         };
2592         // Garbage collection
2593         scope.$on('$destroy', function () {
2594           select.destroy();
2595           options = null;
2596           select = null;
2597         });
2598       }
2599     };
2600   }
2601 ]);
2602
2603 // Source: tab.js
2604 angular.module('mgcrea.ngStrap.tab', []).run([
2605   '$templateCache',
2606   function ($templateCache) {
2607     $templateCache.put('$pane', '{{pane.content}}');
2608   }
2609 ]).provider('$tab', function () {
2610   var defaults = this.defaults = {
2611       animation: 'am-fade',
2612       template: 'tab/tab.tpl.html'
2613     };
2614   this.$get = function () {
2615     return { defaults: defaults };
2616   };
2617 }).directive('bsTabs', [
2618   '$window',
2619   '$animate',
2620   '$tab',
2621   function ($window, $animate, $tab) {
2622     var defaults = $tab.defaults;
2623     return {
2624       restrict: 'EAC',
2625       scope: true,
2626       require: '?ngModel',
2627       templateUrl: function (element, attr) {
2628         return attr.template || defaults.template;
2629       },
2630       link: function postLink(scope, element, attr, controller) {
2631         // Directive options
2632         var options = defaults;
2633         angular.forEach(['animation'], function (key) {
2634           if (angular.isDefined(attr[key]))
2635             options[key] = attr[key];
2636         });
2637         // Require scope as an object
2638         attr.bsTabs && scope.$watch(attr.bsTabs, function (newValue, oldValue) {
2639           scope.panes = newValue;
2640         }, true);
2641         // Add base class
2642         element.addClass('tabs');
2643         // Support animations
2644         if (options.animation) {
2645           element.addClass(options.animation);
2646         }
2647         scope.active = scope.activePane = 0;
2648         // view -> model
2649         scope.setActive = function (index, ev) {
2650           scope.active = index;
2651           if (controller) {
2652             controller.$setViewValue(index);
2653           }
2654         };
2655         // model -> view
2656         if (controller) {
2657           controller.$render = function () {
2658             scope.active = controller.$modelValue * 1;
2659           };
2660         }
2661       }
2662     };
2663   }
2664 ]);
2665
2666 // Source: timepicker.js
2667 angular.module('mgcrea.ngStrap.timepicker', [
2668   'mgcrea.ngStrap.helpers.dateParser',
2669   'mgcrea.ngStrap.tooltip'
2670 ]).provider('$timepicker', function () {
2671   var defaults = this.defaults = {
2672       animation: 'am-fade',
2673       prefixClass: 'timepicker',
2674       placement: 'bottom-left',
2675       template: 'timepicker/timepicker.tpl.html',
2676       trigger: 'focus',
2677       container: false,
2678       keyboard: true,
2679       html: false,
2680       delay: 0,
2681       useNative: true,
2682       timeType: 'date',
2683       timeFormat: 'shortTime',
2684       modelTimeFormat: null,
2685       autoclose: false,
2686       minTime: -Infinity,
2687       maxTime: +Infinity,
2688       length: 5,
2689       hourStep: 1,
2690       minuteStep: 5,
2691       iconUp: 'glyphicon glyphicon-chevron-up',
2692       iconDown: 'glyphicon glyphicon-chevron-down'
2693     };
2694   this.$get = [
2695     '$window',
2696     '$document',
2697     '$rootScope',
2698     '$sce',
2699     '$locale',
2700     'dateFilter',
2701     '$tooltip',
2702     function ($window, $document, $rootScope, $sce, $locale, dateFilter, $tooltip) {
2703       var bodyEl = angular.element($window.document.body);
2704       var isTouch = 'createTouch' in $window.document;
2705       var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2706       if (!defaults.lang)
2707         defaults.lang = $locale.id;
2708       function timepickerFactory(element, controller, config) {
2709         var $timepicker = $tooltip(element, angular.extend({}, defaults, config));
2710         var parentScope = config.scope;
2711         var options = $timepicker.$options;
2712         var scope = $timepicker.$scope;
2713         // View vars
2714         var selectedIndex = 0;
2715         var startDate = controller.$dateValue || new Date();
2716         var viewDate = {
2717             hour: startDate.getHours(),
2718             meridian: startDate.getHours() < 12,
2719             minute: startDate.getMinutes(),
2720             second: startDate.getSeconds(),
2721             millisecond: startDate.getMilliseconds()
2722           };
2723         var format = $locale.DATETIME_FORMATS[options.timeFormat] || options.timeFormat;
2724         var formats = /(h+)([:\.])?(m+)[ ]?(a?)/i.exec(format).slice(1);
2725         scope.$iconUp = options.iconUp;
2726         scope.$iconDown = options.iconDown;
2727         // Scope methods
2728         scope.$select = function (date, index) {
2729           $timepicker.select(date, index);
2730         };
2731         scope.$moveIndex = function (value, index) {
2732           $timepicker.$moveIndex(value, index);
2733         };
2734         scope.$switchMeridian = function (date) {
2735           $timepicker.switchMeridian(date);
2736         };
2737         // Public methods
2738         $timepicker.update = function (date) {
2739           // console.warn('$timepicker.update() newValue=%o', date);
2740           if (angular.isDate(date) && !isNaN(date.getTime())) {
2741             $timepicker.$date = date;
2742             angular.extend(viewDate, {
2743               hour: date.getHours(),
2744               minute: date.getMinutes(),
2745               second: date.getSeconds(),
2746               millisecond: date.getMilliseconds()
2747             });
2748             $timepicker.$build();
2749           } else if (!$timepicker.$isBuilt) {
2750             $timepicker.$build();
2751           }
2752         };
2753         $timepicker.select = function (date, index, keep) {
2754           // console.warn('$timepicker.select', date, scope.$mode);
2755           if (!controller.$dateValue || isNaN(controller.$dateValue.getTime()))
2756             controller.$dateValue = new Date(1970, 0, 1);
2757           if (!angular.isDate(date))
2758             date = new Date(date);
2759           if (index === 0)
2760             controller.$dateValue.setHours(date.getHours());
2761           else if (index === 1)
2762             controller.$dateValue.setMinutes(date.getMinutes());
2763           controller.$setViewValue(controller.$dateValue);
2764           controller.$render();
2765           if (options.autoclose && !keep) {
2766             $timepicker.hide(true);
2767           }
2768         };
2769         $timepicker.switchMeridian = function (date) {
2770           var hours = (date || controller.$dateValue).getHours();
2771           controller.$dateValue.setHours(hours < 12 ? hours + 12 : hours - 12);
2772           controller.$setViewValue(controller.$dateValue);
2773           controller.$render();
2774         };
2775         // Protected methods
2776         $timepicker.$build = function () {
2777           // console.warn('$timepicker.$build() viewDate=%o', viewDate);
2778           var i, midIndex = scope.midIndex = parseInt(options.length / 2, 10);
2779           var hours = [], hour;
2780           for (i = 0; i < options.length; i++) {
2781             hour = new Date(1970, 0, 1, viewDate.hour - (midIndex - i) * options.hourStep);
2782             hours.push({
2783               date: hour,
2784               label: dateFilter(hour, formats[0]),
2785               selected: $timepicker.$date && $timepicker.$isSelected(hour, 0),
2786               disabled: $timepicker.$isDisabled(hour, 0)
2787             });
2788           }
2789           var minutes = [], minute;
2790           for (i = 0; i < options.length; i++) {
2791             minute = new Date(1970, 0, 1, 0, viewDate.minute - (midIndex - i) * options.minuteStep);
2792             minutes.push({
2793               date: minute,
2794               label: dateFilter(minute, formats[2]),
2795               selected: $timepicker.$date && $timepicker.$isSelected(minute, 1),
2796               disabled: $timepicker.$isDisabled(minute, 1)
2797             });
2798           }
2799           var rows = [];
2800           for (i = 0; i < options.length; i++) {
2801             rows.push([
2802               hours[i],
2803               minutes[i]
2804             ]);
2805           }
2806           scope.rows = rows;
2807           scope.showAM = !!formats[3];
2808           scope.isAM = ($timepicker.$date || hours[midIndex].date).getHours() < 12;
2809           scope.timeSeparator = formats[1];
2810           $timepicker.$isBuilt = true;
2811         };
2812         $timepicker.$isSelected = function (date, index) {
2813           if (!$timepicker.$date)
2814             return false;
2815           else if (index === 0) {
2816             return date.getHours() === $timepicker.$date.getHours();
2817           } else if (index === 1) {
2818             return date.getMinutes() === $timepicker.$date.getMinutes();
2819           }
2820         };
2821         $timepicker.$isDisabled = function (date, index) {
2822           var selectedTime;
2823           if (index === 0) {
2824             selectedTime = date.getTime() + viewDate.minute * 60000;
2825           } else if (index === 1) {
2826             selectedTime = date.getTime() + viewDate.hour * 3600000;
2827           }
2828           return selectedTime < options.minTime * 1 || selectedTime > options.maxTime * 1;
2829         };
2830         $timepicker.$moveIndex = function (value, index) {
2831           var targetDate;
2832           if (index === 0) {
2833             targetDate = new Date(1970, 0, 1, viewDate.hour + value * options.length, viewDate.minute);
2834             angular.extend(viewDate, { hour: targetDate.getHours() });
2835           } else if (index === 1) {
2836             targetDate = new Date(1970, 0, 1, viewDate.hour, viewDate.minute + value * options.length * options.minuteStep);
2837             angular.extend(viewDate, { minute: targetDate.getMinutes() });
2838           }
2839           $timepicker.$build();
2840         };
2841         $timepicker.$onMouseDown = function (evt) {
2842           // Prevent blur on mousedown on .dropdown-menu
2843           if (evt.target.nodeName.toLowerCase() !== 'input')
2844             evt.preventDefault();
2845           evt.stopPropagation();
2846           // Emulate click for mobile devices
2847           if (isTouch) {
2848             var targetEl = angular.element(evt.target);
2849             if (targetEl[0].nodeName.toLowerCase() !== 'button') {
2850               targetEl = targetEl.parent();
2851             }
2852             targetEl.triggerHandler('click');
2853           }
2854         };
2855         $timepicker.$onKeyDown = function (evt) {
2856           if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
2857             return;
2858           evt.preventDefault();
2859           evt.stopPropagation();
2860           // Close on enter
2861           if (evt.keyCode === 13)
2862             return $timepicker.hide(true);
2863           // Navigate with keyboard
2864           var newDate = new Date($timepicker.$date);
2865           var hours = newDate.getHours(), hoursLength = dateFilter(newDate, 'h').length;
2866           var minutes = newDate.getMinutes(), minutesLength = dateFilter(newDate, 'mm').length;
2867           var lateralMove = /(37|39)/.test(evt.keyCode);
2868           var count = 2 + !!formats[3] * 1;
2869           // Navigate indexes (left, right)
2870           if (lateralMove) {
2871             if (evt.keyCode === 37)
2872               selectedIndex = selectedIndex < 1 ? count - 1 : selectedIndex - 1;
2873             else if (evt.keyCode === 39)
2874               selectedIndex = selectedIndex < count - 1 ? selectedIndex + 1 : 0;
2875           }
2876           // Update values (up, down)
2877           var selectRange = [
2878               0,
2879               hoursLength
2880             ];
2881           if (selectedIndex === 0) {
2882             if (evt.keyCode === 38)
2883               newDate.setHours(hours - parseInt(options.hourStep, 10));
2884             else if (evt.keyCode === 40)
2885               newDate.setHours(hours + parseInt(options.hourStep, 10));
2886             selectRange = [
2887               0,
2888               hoursLength
2889             ];
2890           } else if (selectedIndex === 1) {
2891             if (evt.keyCode === 38)
2892               newDate.setMinutes(minutes - parseInt(options.minuteStep, 10));
2893             else if (evt.keyCode === 40)
2894               newDate.setMinutes(minutes + parseInt(options.minuteStep, 10));
2895             selectRange = [
2896               hoursLength + 1,
2897               hoursLength + 1 + minutesLength
2898             ];
2899           } else if (selectedIndex === 2) {
2900             if (!lateralMove)
2901               $timepicker.switchMeridian();
2902             selectRange = [
2903               hoursLength + 1 + minutesLength + 1,
2904               hoursLength + 1 + minutesLength + 3
2905             ];
2906           }
2907           $timepicker.select(newDate, selectedIndex, true);
2908           createSelection(selectRange[0], selectRange[1]);
2909           parentScope.$digest();
2910         };
2911         // Private
2912         function createSelection(start, end) {
2913           if (element[0].createTextRange) {
2914             var selRange = element[0].createTextRange();
2915             selRange.collapse(true);
2916             selRange.moveStart('character', start);
2917             selRange.moveEnd('character', end);
2918             selRange.select();
2919           } else if (element[0].setSelectionRange) {
2920             element[0].setSelectionRange(start, end);
2921           } else if (angular.isUndefined(element[0].selectionStart)) {
2922             element[0].selectionStart = start;
2923             element[0].selectionEnd = end;
2924           }
2925         }
2926         function focusElement() {
2927           element[0].focus();
2928         }
2929         // Overrides
2930         var _init = $timepicker.init;
2931         $timepicker.init = function () {
2932           if (isNative && options.useNative) {
2933             element.prop('type', 'time');
2934             element.css('-webkit-appearance', 'textfield');
2935             return;
2936           } else if (isTouch) {
2937             element.prop('type', 'text');
2938             element.attr('readonly', 'true');
2939             element.on('click', focusElement);
2940           }
2941           _init();
2942         };
2943         var _destroy = $timepicker.destroy;
2944         $timepicker.destroy = function () {
2945           if (isNative && options.useNative) {
2946             element.off('click', focusElement);
2947           }
2948           _destroy();
2949         };
2950         var _show = $timepicker.show;
2951         $timepicker.show = function () {
2952           _show();
2953           setTimeout(function () {
2954             $timepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2955             if (options.keyboard) {
2956               element.on('keydown', $timepicker.$onKeyDown);
2957             }
2958           });
2959         };
2960         var _hide = $timepicker.hide;
2961         $timepicker.hide = function (blur) {
2962           $timepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $timepicker.$onMouseDown);
2963           if (options.keyboard) {
2964             element.off('keydown', $timepicker.$onKeyDown);
2965           }
2966           _hide(blur);
2967         };
2968         return $timepicker;
2969       }
2970       timepickerFactory.defaults = defaults;
2971       return timepickerFactory;
2972     }
2973   ];
2974 }).directive('bsTimepicker', [
2975   '$window',
2976   '$parse',
2977   '$q',
2978   '$locale',
2979   'dateFilter',
2980   '$timepicker',
2981   '$dateParser',
2982   '$timeout',
2983   function ($window, $parse, $q, $locale, dateFilter, $timepicker, $dateParser, $timeout) {
2984     var defaults = $timepicker.defaults;
2985     var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
2986     var requestAnimationFrame = $window.requestAnimationFrame || $window.setTimeout;
2987     return {
2988       restrict: 'EAC',
2989       require: 'ngModel',
2990       link: function postLink(scope, element, attr, controller) {
2991         // Directive options
2992         var options = {
2993             scope: scope,
2994             controller: controller
2995           };
2996         angular.forEach([
2997           'placement',
2998           'container',
2999           'delay',
3000           'trigger',
3001           'keyboard',
3002           'html',
3003           'animation',
3004           'template',
3005           'autoclose',
3006           'timeType',
3007           'timeFormat',
3008           'modelTimeFormat',
3009           'useNative',
3010           'hourStep',
3011           'minuteStep',
3012           'length'
3013         ], function (key) {
3014           if (angular.isDefined(attr[key]))
3015             options[key] = attr[key];
3016         });
3017         // Initialize timepicker
3018         if (isNative && (options.useNative || defaults.useNative))
3019           options.timeFormat = 'HH:mm';
3020         var timepicker = $timepicker(element, controller, options);
3021         options = timepicker.$options;
3022         // Initialize parser
3023         var dateParser = $dateParser({
3024             format: options.timeFormat,
3025             lang: options.lang
3026           });
3027         // Observe attributes for changes
3028         angular.forEach([
3029           'minTime',
3030           'maxTime'
3031         ], function (key) {
3032           // console.warn('attr.$observe(%s)', key, attr[key]);
3033           angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
3034             if (newValue === 'now') {
3035               timepicker.$options[key] = new Date().setFullYear(1970, 0, 1);
3036             } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
3037               timepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
3038             } else {
3039               timepicker.$options[key] = dateParser.parse(newValue, new Date(1970, 0, 1, 0));
3040             }
3041             !isNaN(timepicker.$options[key]) && timepicker.$build();
3042           });
3043         });
3044         // Watch model for changes
3045         scope.$watch(attr.ngModel, function (newValue, oldValue) {
3046           // console.warn('scope.$watch(%s)', attr.ngModel, newValue, oldValue, controller.$dateValue);
3047           timepicker.update(controller.$dateValue);
3048         }, true);
3049         // viewValue -> $parsers -> modelValue
3050         controller.$parsers.unshift(function (viewValue) {
3051           // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
3052           // Null values should correctly reset the model value & validity
3053           if (!viewValue) {
3054             controller.$setValidity('date', true);
3055             return;
3056           }
3057           var parsedTime = dateParser.parse(viewValue, controller.$dateValue);
3058           if (!parsedTime || isNaN(parsedTime.getTime())) {
3059             controller.$setValidity('date', false);
3060           } else {
3061             var isValid = parsedTime.getTime() >= options.minTime && parsedTime.getTime() <= options.maxTime;
3062             controller.$setValidity('date', isValid);
3063             // Only update the model when we have a valid date
3064             if (isValid)
3065               controller.$dateValue = parsedTime;
3066           }
3067           if (options.timeType === 'string') {
3068             return dateFilter(parsedTime, options.modelTimeFormat || options.timeFormat);
3069           } else if (options.timeType === 'number') {
3070             return controller.$dateValue.getTime();
3071           } else if (options.timeType === 'iso') {
3072             return controller.$dateValue.toISOString();
3073           } else {
3074             return new Date(controller.$dateValue);
3075           }
3076         });
3077         // modelValue -> $formatters -> viewValue
3078         controller.$formatters.push(function (modelValue) {
3079           // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
3080           var date;
3081           if (angular.isUndefined(modelValue) || modelValue === null) {
3082             date = NaN;
3083           } else if (angular.isDate(modelValue)) {
3084             date = modelValue;
3085           } else if (options.timeType === 'string') {
3086             date = dateParser.parse(modelValue, null, options.modelTimeFormat);
3087           } else {
3088             date = new Date(modelValue);
3089           }
3090           // Setup default value?
3091           // if(isNaN(date.getTime())) date = new Date(new Date().setMinutes(0) + 36e5);
3092           controller.$dateValue = date;
3093           return controller.$dateValue;
3094         });
3095         // viewValue -> element
3096         controller.$render = function () {
3097           // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
3098           element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.timeFormat));
3099         };
3100         // Garbage collection
3101         scope.$on('$destroy', function () {
3102           timepicker.destroy();
3103           options = null;
3104           timepicker = null;
3105         });
3106       }
3107     };
3108   }
3109 ]);
3110
3111 // Source: tooltip.js
3112 angular.module('mgcrea.ngStrap.tooltip', ['mgcrea.ngStrap.helpers.dimensions']).provider('$tooltip', function () {
3113   var defaults = this.defaults = {
3114       animation: 'am-fade',
3115       prefixClass: 'tooltip',
3116       prefixEvent: 'tooltip',
3117       container: false,
3118       target: false,
3119       placement: 'top',
3120       template: 'tooltip/tooltip.tpl.html',
3121       contentTemplate: false,
3122       trigger: 'hover focus',
3123       keyboard: false,
3124       html: false,
3125       show: false,
3126       title: '',
3127       type: '',
3128       delay: 0
3129     };
3130   this.$get = [
3131     '$window',
3132     '$rootScope',
3133     '$compile',
3134     '$q',
3135     '$templateCache',
3136     '$http',
3137     '$animate',
3138     'dimensions',
3139     '$$rAF',
3140     function ($window, $rootScope, $compile, $q, $templateCache, $http, $animate, dimensions, $$rAF) {
3141       var trim = String.prototype.trim;
3142       var isTouch = 'createTouch' in $window.document;
3143       var htmlReplaceRegExp = /ng-bind="/gi;
3144       function TooltipFactory(element, config) {
3145         var $tooltip = {};
3146         // Common vars
3147         var nodeName = element[0].nodeName.toLowerCase();
3148         var options = $tooltip.$options = angular.extend({}, defaults, config);
3149         $tooltip.$promise = fetchTemplate(options.template);
3150         var scope = $tooltip.$scope = options.scope && options.scope.$new() || $rootScope.$new();
3151         if (options.delay && angular.isString(options.delay)) {
3152           options.delay = parseFloat(options.delay);
3153         }
3154         // Support scope as string options
3155         if (options.title) {
3156           $tooltip.$scope.title = options.title;
3157         }
3158         // Provide scope helpers
3159         scope.$hide = function () {
3160           scope.$$postDigest(function () {
3161             $tooltip.hide();
3162           });
3163         };
3164         scope.$show = function () {
3165           scope.$$postDigest(function () {
3166             $tooltip.show();
3167           });
3168         };
3169         scope.$toggle = function () {
3170           scope.$$postDigest(function () {
3171             $tooltip.toggle();
3172           });
3173         };
3174         $tooltip.$isShown = scope.$isShown = false;
3175         // Private vars
3176         var timeout, hoverState;
3177         // Support contentTemplate option
3178         if (options.contentTemplate) {
3179           $tooltip.$promise = $tooltip.$promise.then(function (template) {
3180             var templateEl = angular.element(template);
3181             return fetchTemplate(options.contentTemplate).then(function (contentTemplate) {
3182               var contentEl = findElement('[ng-bind="content"]', templateEl[0]);
3183               if (!contentEl.length)
3184                 contentEl = findElement('[ng-bind="title"]', templateEl[0]);
3185               contentEl.removeAttr('ng-bind').html(contentTemplate);
3186               return templateEl[0].outerHTML;
3187             });
3188           });
3189         }
3190         // Fetch, compile then initialize tooltip
3191         var tipLinker, tipElement, tipTemplate, tipContainer;
3192         $tooltip.$promise.then(function (template) {
3193           if (angular.isObject(template))
3194             template = template.data;
3195           if (options.html)
3196             template = template.replace(htmlReplaceRegExp, 'ng-bind-html="');
3197           template = trim.apply(template);
3198           tipTemplate = template;
3199           tipLinker = $compile(template);
3200           $tooltip.init();
3201         });
3202         $tooltip.init = function () {
3203           // Options: delay
3204           if (options.delay && angular.isNumber(options.delay)) {
3205             options.delay = {
3206               show: options.delay,
3207               hide: options.delay
3208             };
3209           }
3210           // Replace trigger on touch devices ?
3211           // if(isTouch && options.trigger === defaults.trigger) {
3212           //   options.trigger.replace(/hover/g, 'click');
3213           // }
3214           // Options : container
3215           if (options.container === 'self') {
3216             tipContainer = element;
3217           } else if (options.container) {
3218             tipContainer = findElement(options.container);
3219           }
3220           // Options: trigger
3221           var triggers = options.trigger.split(' ');
3222           angular.forEach(triggers, function (trigger) {
3223             if (trigger === 'click') {
3224               element.on('click', $tooltip.toggle);
3225             } else if (trigger !== 'manual') {
3226               element.on(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3227               element.on(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3228               nodeName === 'button' && trigger !== 'hover' && element.on(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3229             }
3230           });
3231           // Options: target
3232           if (options.target) {
3233             options.target = angular.isElement(options.target) ? options.target : findElement(options.target)[0];
3234           }
3235           // Options: show
3236           if (options.show) {
3237             scope.$$postDigest(function () {
3238               options.trigger === 'focus' ? element[0].focus() : $tooltip.show();
3239             });
3240           }
3241         };
3242         $tooltip.destroy = function () {
3243           // Unbind events
3244           var triggers = options.trigger.split(' ');
3245           for (var i = triggers.length; i--;) {
3246             var trigger = triggers[i];
3247             if (trigger === 'click') {
3248               element.off('click', $tooltip.toggle);
3249             } else if (trigger !== 'manual') {
3250               element.off(trigger === 'hover' ? 'mouseenter' : 'focus', $tooltip.enter);
3251               element.off(trigger === 'hover' ? 'mouseleave' : 'blur', $tooltip.leave);
3252               nodeName === 'button' && trigger !== 'hover' && element.off(isTouch ? 'touchstart' : 'mousedown', $tooltip.$onFocusElementMouseDown);
3253             }
3254           }
3255           // Remove element
3256           if (tipElement) {
3257             tipElement.remove();
3258             tipElement = null;
3259           }
3260           // Cancel pending callbacks
3261           clearTimeout(timeout);
3262           // Destroy scope
3263           scope.$destroy();
3264         };
3265         $tooltip.enter = function () {
3266           clearTimeout(timeout);
3267           hoverState = 'in';
3268           if (!options.delay || !options.delay.show) {
3269             return $tooltip.show();
3270           }
3271           timeout = setTimeout(function () {
3272             if (hoverState === 'in')
3273               $tooltip.show();
3274           }, options.delay.show);
3275         };
3276         $tooltip.show = function () {
3277           scope.$emit(options.prefixEvent + '.show.before', $tooltip);
3278           var parent = options.container ? tipContainer : null;
3279           var after = options.container ? null : element;
3280           // Hide any existing tipElement
3281           if (tipElement)
3282             tipElement.remove();
3283           // Fetch a cloned element linked from template
3284           tipElement = $tooltip.$element = tipLinker(scope, function (clonedElement, scope) {
3285           });
3286           // Set the initial positioning.
3287           tipElement.css({
3288             top: '-9999px',
3289             left: '-9999px',
3290             display: 'block'
3291           }).addClass(options.placement);
3292           // Options: animation
3293           if (options.animation)
3294             tipElement.addClass(options.animation);
3295           // Options: type
3296           if (options.type)
3297             tipElement.addClass(options.prefixClass + '-' + options.type);
3298           $animate.enter(tipElement, parent, after, function () {
3299             scope.$emit(options.prefixEvent + '.show', $tooltip);
3300           });
3301           $tooltip.$isShown = scope.$isShown = true;
3302           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3303           $$rAF($tooltip.$applyPlacement);
3304           // var a = bodyEl.offsetWidth + 1; ?
3305           // Bind events
3306           if (options.keyboard) {
3307             if (options.trigger !== 'focus') {
3308               $tooltip.focus();
3309               tipElement.on('keyup', $tooltip.$onKeyUp);
3310             } else {
3311               element.on('keyup', $tooltip.$onFocusKeyUp);
3312             }
3313           }
3314         };
3315         $tooltip.leave = function () {
3316           clearTimeout(timeout);
3317           hoverState = 'out';
3318           if (!options.delay || !options.delay.hide) {
3319             return $tooltip.hide();
3320           }
3321           timeout = setTimeout(function () {
3322             if (hoverState === 'out') {
3323               $tooltip.hide();
3324             }
3325           }, options.delay.hide);
3326         };
3327         $tooltip.hide = function (blur) {
3328           if (!$tooltip.$isShown)
3329             return;
3330           scope.$emit(options.prefixEvent + '.hide.before', $tooltip);
3331           $animate.leave(tipElement, function () {
3332             scope.$emit(options.prefixEvent + '.hide', $tooltip);
3333           });
3334           $tooltip.$isShown = scope.$isShown = false;
3335           scope.$$phase || scope.$root && scope.$root.$$phase || scope.$digest();
3336           // Unbind events
3337           if (options.keyboard && tipElement !== null) {
3338             tipElement.off('keyup', $tooltip.$onKeyUp);
3339           }
3340           // Allow to blur the input when hidden, like when pressing enter key
3341           if (blur && options.trigger === 'focus') {
3342             return element[0].blur();
3343           }
3344         };
3345         $tooltip.toggle = function () {
3346           $tooltip.$isShown ? $tooltip.leave() : $tooltip.enter();
3347         };
3348         $tooltip.focus = function () {
3349           tipElement[0].focus();
3350         };
3351         // Protected methods
3352         $tooltip.$applyPlacement = function () {
3353           if (!tipElement)
3354             return;
3355           // Get the position of the tooltip element.
3356           var elementPosition = getPosition();
3357           // Get the height and width of the tooltip so we can center it.
3358           var tipWidth = tipElement.prop('offsetWidth'), tipHeight = tipElement.prop('offsetHeight');
3359           // Get the tooltip's top and left coordinates to center it with this directive.
3360           var tipPosition = getCalculatedOffset(options.placement, elementPosition, tipWidth, tipHeight);
3361           // Now set the calculated positioning.
3362           tipPosition.top += 'px';
3363           tipPosition.left += 'px';
3364           tipElement.css(tipPosition);
3365         };
3366         $tooltip.$onKeyUp = function (evt) {
3367           evt.which === 27 && $tooltip.hide();
3368         };
3369         $tooltip.$onFocusKeyUp = function (evt) {
3370           evt.which === 27 && element[0].blur();
3371         };
3372         $tooltip.$onFocusElementMouseDown = function (evt) {
3373           evt.preventDefault();
3374           evt.stopPropagation();
3375           // Some browsers do not auto-focus buttons (eg. Safari)
3376           $tooltip.$isShown ? element[0].blur() : element[0].focus();
3377         };
3378         // Private methods
3379         function getPosition() {
3380           if (options.container === 'body') {
3381             return dimensions.offset(options.target || element[0]);
3382           } else {
3383             return dimensions.position(options.target || element[0]);
3384           }
3385         }
3386         function getCalculatedOffset(placement, position, actualWidth, actualHeight) {
3387           var offset;
3388           var split = placement.split('-');
3389           switch (split[0]) {
3390           case 'right':
3391             offset = {
3392               top: position.top + position.height / 2 - actualHeight / 2,
3393               left: position.left + position.width
3394             };
3395             break;
3396           case 'bottom':
3397             offset = {
3398               top: position.top + position.height,
3399               left: position.left + position.width / 2 - actualWidth / 2
3400             };
3401             break;
3402           case 'left':
3403             offset = {
3404               top: position.top + position.height / 2 - actualHeight / 2,
3405               left: position.left - actualWidth
3406             };
3407             break;
3408           default:
3409             offset = {
3410               top: position.top - actualHeight,
3411               left: position.left + position.width / 2 - actualWidth / 2
3412             };
3413             break;
3414           }
3415           if (!split[1]) {
3416             return offset;
3417           }
3418           // Add support for corners @todo css
3419           if (split[0] === 'top' || split[0] === 'bottom') {
3420             switch (split[1]) {
3421             case 'left':
3422               offset.left = position.left;
3423               break;
3424             case 'right':
3425               offset.left = position.left + position.width - actualWidth;
3426             }
3427           } else if (split[0] === 'left' || split[0] === 'right') {
3428             switch (split[1]) {
3429             case 'top':
3430               offset.top = position.top - actualHeight;
3431               break;
3432             case 'bottom':
3433               offset.top = position.top + position.height;
3434             }
3435           }
3436           return offset;
3437         }
3438         return $tooltip;
3439       }
3440       // Helper functions
3441       function findElement(query, element) {
3442         return angular.element((element || document).querySelectorAll(query));
3443       }
3444       function fetchTemplate(template) {
3445         return $q.when($templateCache.get(template) || $http.get(template)).then(function (res) {
3446           if (angular.isObject(res)) {
3447             $templateCache.put(template, res.data);
3448             return res.data;
3449           }
3450           return res;
3451         });
3452       }
3453       return TooltipFactory;
3454     }
3455   ];
3456 }).directive('bsTooltip', [
3457   '$window',
3458   '$location',
3459   '$sce',
3460   '$tooltip',
3461   '$$rAF',
3462   function ($window, $location, $sce, $tooltip, $$rAF) {
3463     return {
3464       restrict: 'EAC',
3465       scope: true,
3466       link: function postLink(scope, element, attr, transclusion) {
3467         // Directive options
3468         var options = { scope: scope };
3469         angular.forEach([
3470           'template',
3471           'contentTemplate',
3472           'placement',
3473           'container',
3474           'target',
3475           'delay',
3476           'trigger',
3477           'keyboard',
3478           'html',
3479           'animation',
3480           'type'
3481         ], function (key) {
3482           if (angular.isDefined(attr[key]))
3483             options[key] = attr[key];
3484         });
3485         // Observe scope attributes for change
3486         angular.forEach(['title'], function (key) {
3487           attr[key] && attr.$observe(key, function (newValue, oldValue) {
3488             scope[key] = $sce.trustAsHtml(newValue);
3489             angular.isDefined(oldValue) && $$rAF(function () {
3490               tooltip && tooltip.$applyPlacement();
3491             });
3492           });
3493         });
3494         // Support scope as an object
3495         attr.bsTooltip && scope.$watch(attr.bsTooltip, function (newValue, oldValue) {
3496           if (angular.isObject(newValue)) {
3497             angular.extend(scope, newValue);
3498           } else {
3499             scope.title = newValue;
3500           }
3501           angular.isDefined(oldValue) && $$rAF(function () {
3502             tooltip && tooltip.$applyPlacement();
3503           });
3504         }, true);
3505         // Initialize popover
3506         var tooltip = $tooltip(element, options);
3507         // Garbage collection
3508         scope.$on('$destroy', function () {
3509           tooltip.destroy();
3510           options = null;
3511           tooltip = null;
3512         });
3513       }
3514     };
3515   }
3516 ]);
3517
3518 // Source: typeahead.js
3519 angular.module('mgcrea.ngStrap.typeahead', [
3520   'mgcrea.ngStrap.tooltip',
3521   'mgcrea.ngStrap.helpers.parseOptions'
3522 ]).provider('$typeahead', function () {
3523   var defaults = this.defaults = {
3524       animation: 'am-fade',
3525       prefixClass: 'typeahead',
3526       placement: 'bottom-left',
3527       template: 'typeahead/typeahead.tpl.html',
3528       trigger: 'focus',
3529       container: false,
3530       keyboard: true,
3531       html: false,
3532       delay: 0,
3533       minLength: 1,
3534       filter: 'filter',
3535       limit: 6
3536     };
3537   this.$get = [
3538     '$window',
3539     '$rootScope',
3540     '$tooltip',
3541     function ($window, $rootScope, $tooltip) {
3542       var bodyEl = angular.element($window.document.body);
3543       function TypeaheadFactory(element, controller, config) {
3544         var $typeahead = {};
3545         // Common vars
3546         var options = angular.extend({}, defaults, config);
3547         $typeahead = $tooltip(element, options);
3548         var parentScope = config.scope;
3549         var scope = $typeahead.$scope;
3550         scope.$resetMatches = function () {
3551           scope.$matches = [];
3552           scope.$activeIndex = 0;
3553         };
3554         scope.$resetMatches();
3555         scope.$activate = function (index) {
3556           scope.$$postDigest(function () {
3557             $typeahead.activate(index);
3558           });
3559         };
3560         scope.$select = function (index, evt) {
3561           scope.$$postDigest(function () {
3562             $typeahead.select(index);
3563           });
3564         };
3565         scope.$isVisible = function () {
3566           return $typeahead.$isVisible();
3567         };
3568         // Public methods
3569         $typeahead.update = function (matches) {
3570           scope.$matches = matches;
3571           if (scope.$activeIndex >= matches.length) {
3572             scope.$activeIndex = 0;
3573           }
3574         };
3575         $typeahead.activate = function (index) {
3576           scope.$activeIndex = index;
3577         };
3578         $typeahead.select = function (index) {
3579           var value = scope.$matches[index].value;
3580           controller.$setViewValue(value);
3581           controller.$render();
3582           scope.$resetMatches();
3583           if (parentScope)
3584             parentScope.$digest();
3585           // Emit event
3586           scope.$emit('$typeahead.select', value, index);
3587         };
3588         // Protected methods
3589         $typeahead.$isVisible = function () {
3590           if (!options.minLength || !controller) {
3591             return !!scope.$matches.length;
3592           }
3593           // minLength support
3594           return scope.$matches.length && angular.isString(controller.$viewValue) && controller.$viewValue.length >= options.minLength;
3595         };
3596         $typeahead.$getIndex = function (value) {
3597           var l = scope.$matches.length, i = l;
3598           if (!l)
3599             return;
3600           for (i = l; i--;) {
3601             if (scope.$matches[i].value === value)
3602               break;
3603           }
3604           if (i < 0)
3605             return;
3606           return i;
3607         };
3608         $typeahead.$onMouseDown = function (evt) {
3609           // Prevent blur on mousedown
3610           evt.preventDefault();
3611           evt.stopPropagation();
3612         };
3613         $typeahead.$onKeyDown = function (evt) {
3614           if (!/(38|40|13)/.test(evt.keyCode))
3615             return;
3616           evt.preventDefault();
3617           evt.stopPropagation();
3618           // Select with enter
3619           if (evt.keyCode === 13 && scope.$matches.length) {
3620             $typeahead.select(scope.$activeIndex);
3621           }  // Navigate with keyboard
3622           else if (evt.keyCode === 38 && scope.$activeIndex > 0)
3623             scope.$activeIndex--;
3624           else if (evt.keyCode === 40 && scope.$activeIndex < scope.$matches.length - 1)
3625             scope.$activeIndex++;
3626           else if (angular.isUndefined(scope.$activeIndex))
3627             scope.$activeIndex = 0;
3628           scope.$digest();
3629         };
3630         // Overrides
3631         var show = $typeahead.show;
3632         $typeahead.show = function () {
3633           show();
3634           setTimeout(function () {
3635             $typeahead.$element.on('mousedown', $typeahead.$onMouseDown);
3636             if (options.keyboard) {
3637               element.on('keydown', $typeahead.$onKeyDown);
3638             }
3639           });
3640         };
3641         var hide = $typeahead.hide;
3642         $typeahead.hide = function () {
3643           $typeahead.$element.off('mousedown', $typeahead.$onMouseDown);
3644           if (options.keyboard) {
3645             element.off('keydown', $typeahead.$onKeyDown);
3646           }
3647           hide();
3648         };
3649         return $typeahead;
3650       }
3651       TypeaheadFactory.defaults = defaults;
3652       return TypeaheadFactory;
3653     }
3654   ];
3655 }).directive('bsTypeahead', [
3656   '$window',
3657   '$parse',
3658   '$q',
3659   '$typeahead',
3660   '$parseOptions',
3661   function ($window, $parse, $q, $typeahead, $parseOptions) {
3662     var defaults = $typeahead.defaults;
3663     return {
3664       restrict: 'EAC',
3665       require: 'ngModel',
3666       link: function postLink(scope, element, attr, controller) {
3667         // Directive options
3668         var options = { scope: scope };
3669         angular.forEach([
3670           'placement',
3671           'container',
3672           'delay',
3673           'trigger',
3674           'keyboard',
3675           'html',
3676           'animation',
3677           'template',
3678           'filter',
3679           'limit',
3680           'minLength'
3681         ], function (key) {
3682           if (angular.isDefined(attr[key]))
3683             options[key] = attr[key];
3684         });
3685         // Build proper ngOptions
3686         var filter = options.filter || defaults.filter;
3687         var limit = options.limit || defaults.limit;
3688         var ngOptions = attr.ngOptions;
3689         if (filter)
3690           ngOptions += ' | ' + filter + ':$viewValue';
3691         if (limit)
3692           ngOptions += ' | limitTo:' + limit;
3693         var parsedOptions = $parseOptions(ngOptions);
3694         // Initialize typeahead
3695         var typeahead = $typeahead(element, controller, options);
3696         // Watch model for changes
3697         scope.$watch(attr.ngModel, function (newValue, oldValue) {
3698           // console.warn('$watch', element.attr('ng-model'), newValue);
3699           scope.$modelValue = newValue;
3700           // Publish modelValue on scope for custom templates
3701           parsedOptions.valuesFn(scope, controller).then(function (values) {
3702             if (values.length > limit)
3703               values = values.slice(0, limit);
3704             // Do not re-queue an update if a correct value has been selected
3705             if (values.length === 1 && values[0].value === newValue)
3706               return;
3707             typeahead.update(values);
3708             // Queue a new rendering that will leverage collection loading
3709             controller.$render();
3710           });
3711         });
3712         // Model rendering in view
3713         controller.$render = function () {
3714           // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
3715           if (controller.$isEmpty(controller.$viewValue))
3716             return element.val('');
3717           var index = typeahead.$getIndex(controller.$modelValue);
3718           var selected = angular.isDefined(index) ? typeahead.$scope.$matches[index].label : controller.$viewValue;
3719           selected = angular.isObject(selected) ? selected.label : selected;
3720           element.val(selected.replace(/<(?:.|\n)*?>/gm, '').trim());
3721         };
3722         // Garbage collection
3723         scope.$on('$destroy', function () {
3724           typeahead.destroy();
3725           options = null;
3726           typeahead = null;
3727         });
3728       }
3729     };
3730   }
3731 ]);
3732
3733 })(window, document);