bosun.org@v0.0.0-20210513094433-e25bc3e69a1f/annotate/web/static/js/angular-strap.js (about)

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