boutils il y a 10 ans
Parent
commit
e715430376
8 fichiers modifiés avec 1377 ajouts et 543 suppressions
  1. 0 489
      css/main.css
  2. 523 0
      css/main.less
  3. 1 0
      html/typeahead-match.html
  4. 5 0
      html/typeahead-popup.html
  5. 45 10
      index.html
  6. 25 35
      js/app.js
  7. 0 9
      js/bootstrap-typeahead.min.js
  8. 778 0
      js/typeahead.js

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 489
css/main.css


+ 523 - 0
css/main.less

@@ -0,0 +1,523 @@
+html {
+  height: 100%;
+  width: 100%;
+}
+
+body {
+  background-color: #333;
+  background: url("../img/background.png") no-repeat top center fixed;
+  background-size: cover;
+  margin: 0;
+  padding: 0;
+  height: 100%;
+  width: 100%;
+  overflow: auto;
+}
+
+a {
+  text-decoration: none;
+}
+
+.bg-warning {
+  color: #8a6d3b;
+  background-color: #fcf8e3;
+  border-color: #faebcc;
+  padding: 10px;
+  margin: 1em;
+}
+
+.bg-info {
+  color: #31708f;
+  background-color: #d9edf7;
+  border-color: #bce8f1;
+  padding: 10px;
+  margin: 1em;
+  text-align: center;
+}
+
+.title {
+  position: absolute;
+  z-index: 9999999999;
+  cursor: pointer;
+}
+
+.data-loading-txt {
+  position: relative;
+  top: -40px;
+  left: 50px;
+  font-size: 15px;
+  font-weight: bold;
+  color: #666;
+}
+
+#result {
+  background: white;
+  opacity: 0.9;
+  height: 0px;
+  overflow: hidden;
+  background: url("../img/filter.png") bottom left repeat-x;
+}
+
+#result .result-container {
+  height: 100%;
+  margin: 0;
+  padding: 1em;
+  box-sizing: border-box;
+}
+
+#result .result-box {
+  background: white;
+  border-radius: 10px;
+  height: calc(~'100% - 15px');
+}
+
+.box-header {
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+}
+
+.box-description {
+  height: 80px;
+  overflow: auto;
+}
+
+.box-description-more {
+  text-align: right;
+}
+
+.box-eligibilite-title {
+  text-transform: uppercase;
+  color: #666;
+}
+
+.box-eligibilite-title .btn{
+  float: right;
+  z-index: 1000;
+  position: relative;
+}
+
+.box-eligibilite-yes {
+  color: #8cc474;
+}
+
+.box-eligibilite-no {
+  color: #e46f61;
+}
+
+.box-eligibilite {
+  height: calc(~'100% - 140px');
+}
+
+.box-eligibilite > div {
+  height: 100%;
+}
+
+.box-eligibilite-spinner {
+  text-align: center;
+  position:relative;
+  top: 50%;
+  margin-top: -28px;
+}
+
+.box-eligibilite-none {
+  text-align: center;
+  position:relative;
+  top: 50%;
+  font-size: 25px;
+  text-transform: uppercase;
+  color: #bbb;
+  margin-top: -35px;
+}
+
+.box-body {
+  overflow: auto;
+  padding: 1em;
+  box-sizing: border-box;
+  height: calc(~'100% - 100px');
+  border-left: 7px solid silver;
+  border-radius: 0 0 0 10px;
+}
+
+hr {
+  margin-top: 5px !important;
+  margin-bottom: 5px !important;
+}
+
+.box-logo,
+.box-title-description {
+  display: table-cell;
+  vertical-align: top;
+}
+
+.box-title {
+  text-align: center;
+  font-size: 24px;
+  white-space: nowrap;
+  font-weight: 200;
+  margin-top: 5px;
+}
+
+.box-desc {
+  height: 60px;
+  overflow: auto;
+  padding-left: 4px;
+}
+
+.solution {
+  font-size: 16px;
+}
+
+.box-logo,
+.box-logo img {
+  width: 100px;
+  height: 100px;
+  border-radius: 10px 0 10px 0;
+}
+
+.md-sidenav-right {
+  z-index: 99999999999999;
+}
+
+.md-sidenav-right .md-toolbar-tools {
+  font-size: 20px;
+  position: relative;
+  top: 21px;
+}
+
+.md-sidenav-right md-progress-circular {
+  position: absolute;
+  right: 7px;
+  top: 7px;
+}
+
+.force-hidden .md-inner {
+  display: none !important;
+}
+
+.md-sidenav-right md-progress-circular .md-right .md-half-circle {
+  border-right-color: white !important;
+  border-top-color: white !important;
+}
+
+.md-sidenav-right md-progress-circular .md-left .md-half-circle {
+  border-left-color: white !important;
+  border-top-color: white !important;
+}
+
+.md-sidenav-right md-progress-circular .md-inner {
+  display: none;
+}
+
+
+.md-label:before {
+  content: no-close-quote;
+}
+
+.md-sidenav-right .section-title {
+  text-transform: uppercase;
+  color: #999;
+  white-space: nowrap;
+  font-size: 13px;
+  padding-bottom: 10px;
+  margin-bottom: 7px;
+  border-bottom: 1px solid #999;
+}
+
+
+.md-sidenav-rightmd-checkbox .md-label {
+  position: relative;
+  top: -2px;
+}
+
+md-checkbox {
+  margin: 5px 0 0 0;
+}
+
+.address-bar {
+  position: absolute;
+  right: 20px;
+  z-index: 1000;
+}
+
+.address-bar .input-type {
+  position: absolute;
+  top: 20px;
+  right: 128px;
+
+  .loading-typeahead-results,
+  .no-typeahead-results {
+    padding: 0 5px;
+    height: 40px;
+    line-height: 40px;
+    background: white;
+  }
+}
+
+.address-bar input {
+  width: 420px;
+  height: 41px;
+  padding: 0 10px;
+  font-size: 15px;
+  line-height: 30px;
+  color: #777;
+}
+
+.ellipsis {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  width: inherit;
+}
+
+.address-bar ul.dropdown-menu {
+  margin: -1px;
+  border-radius: 0;
+  width: 510px;
+  padding: 0;
+
+  li {
+    height: 40px;
+    line-height: 40px;
+
+    a {
+      .ellipsis;
+      padding: 0px 5px;
+      height: 100%;
+      line-height: 40px;
+    }
+  }
+}
+
+.address-bar button {
+  height: 41px;
+  position: absolute;
+  top: 20px;
+  right: 40px;
+  border-radius: 0 0 0 0 !important;
+  box-shadow: none;
+}
+
+.address-bar .md-menu {
+  position: absolute;
+  top: 10px;
+  right: -12px;
+  cursor: pointer;
+  color: white;
+  font-size: 45px;
+}
+
+.address-bar button i {
+  padding: 0 10px;
+}
+
+.title img {
+  height: 50px;
+  margin: 1em;
+}
+
+#map {
+  height: 100%;
+  width: 100%;
+}
+
+.popup-title {
+  font-weight: bold;
+}
+
+.popup-line {
+  white-space: nowrap;
+  position: relative;
+}
+
+.leaflet-popup-content {
+  width: 380px !important;
+  max-height: 380px;
+  overflow: auto;
+}
+
+.popup-label {
+  color: rgba(0,0,0,0.54);
+  width: inherit;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  overflow: auto;
+  font-size: 12px;
+}
+
+.popup-value {
+  color: #333;
+  font-size: 14px;
+  height: 26px;
+  line-height: 23px;
+  border-bottom: 1px solid #ddd;
+  margin-bottom: 13px;
+}
+
+.popup-small-date {
+  position: absolute;
+  font-size: 10px;
+  right: 0px;
+  top: 20px;
+}
+
+md-toast {
+  padding-top: 10px !important;
+  font-size: 14px !important;
+}
+
+md-toast.md-bottom {
+  bottom: 15px;
+}
+
+md-toast.md-right {
+  right: 15px;
+}
+
+md-radio-group.md-default-theme:focus:not(:empty) {
+  border-color: transparent !important;
+}
+
+md-radio-button {
+  margin: 5px 0px;
+}
+
+.modal-backdrop.in {
+  opacity: 0.7 !important;
+}
+
+.formulaire .section {
+  padding-bottom: 20px;
+}
+
+.formulaire .no-padding {
+  padding-bottom: 0px;
+}
+
+.formulaire .section-name {
+  text-transform: uppercase;
+  font-size: 22px;
+}
+
+.formulaire md-input-container {
+  padding-left: 10px;
+  padding-bottom: 20px;
+}
+
+.formulaire md-select,
+.formulaire input {
+}
+
+.formulaire md-select-value > span,
+.formulaire md-input-container label {
+  height: 16px;
+  margin-bottom: 5px;
+}
+
+.formulaire md-select-value.md-select-placeholder > span {
+  position: relative;
+  top: -2px;
+  font-weight: bold;
+}
+
+.formulaire md-select-value {
+  width: 100%;
+}
+
+.formulaire md-select-value > span {
+  position: relative;
+  top: 6px;
+}
+
+.formulaire md-content {
+  display: flex;
+}
+
+.formulaire md-content,
+.formulaire md-content input {
+  width: 100%;
+  vertical-align: top;
+}
+
+.formulaire md-select,
+.formulaire textarea {
+  width: 100%;
+}
+
+.formulaire .md-select-icon {
+  display: none;
+}
+
+.md-select-menu-container {
+  z-index: 1050;
+}
+
+.form-required {
+  color: rgb(196,59,29);
+}
+
+md-checkbox .md-label:before {
+  display: none;
+}
+
+md-checkbox {
+  padding: 0;
+}
+
+md-checkbox .md-label {
+  margin-left: 25px;
+  line-height: 16px;
+  font-size: 12px;
+}
+
+md-checkbox .md-container {
+  position: absolute;
+  top: 10px;
+}
+
+.formulaire .pres-text {
+  font-size: 12px;
+  line-height: 16px;
+}
+
+#mn_inscription .modal-body {
+  max-height: 400px;
+  overflow: auto;
+  min-height: 150px;
+}
+
+.mn-form-in-progress,
+.mn-form-error,
+.mn-form-ok {
+  text-align: center;
+  font-size: 20px;
+}
+
+.mn-form-error i{
+  color: #EF5050;
+}
+
+.mn-form-ok i{
+  color: #68C782;
+}
+
+.mn-form-in-progress i,
+.mn-form-error i,
+.mn-form-ok i {
+  font-size: 80px;
+  margin-bottom: 30px;
+}
+
+.mn-form md-radio-group,
+.mn-form md-radio-button {
+  display: block;
+}
+
+.mn-form md-radio-button .md-label{
+  white-space: nowrap;
+}
+
+.mn-form .alerte {
+  margin-top: 30px;
+  color: rgb(196,59,29);
+}

+ 1 - 0
html/typeahead-match.html

@@ -0,0 +1 @@
+<a href tabindex="-1" ng-bind-html="match.label | uibTypeaheadHighlight:query"></a>

+ 5 - 0
html/typeahead-popup.html

@@ -0,0 +1,5 @@
+<ul class="dropdown-menu" ng-show="isOpen() && !moveInProgress" ng-style="{top: position().top+'px', left: position().left+'px'}" style="display: block;" role="listbox" aria-hidden="{{!isOpen()}}">
+    <li ng-repeat="match in matches track by $index" ng-class="{active: isActive($index) }" ng-mouseenter="selectActive($index)" ng-click="selectMatch($index)" role="option" id="{{::match.id}}">
+        <div uib-typeahead-match index="$index" match="match" query="query" template-url="templateUrl"></div>
+    </li>
+</ul>

+ 45 - 10
index.html

@@ -52,8 +52,7 @@
     <link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css">
 
     <script src="//netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js"></script>
-    <link rel="stylesheet" href="css/angular.typeahead.css" />
-    <script src="js/bootstrap-typeahead.min.js"></script>
+    <script src="js/typeahead.js"></script>
     <!-- Angular Material Javascript using RawGit to load directly from `bower-material/master` -->
     <script type = "text/javascript" src = "js/angular-material.js"></script>
 
@@ -92,15 +91,51 @@
       <div class = "input-type">
 
         <input
-          type             = "text"
-          placeholder      = "Pour tester votre éligibilité, merci de saisir une adresse"
-          ng-model         = "address"
-          ng-focus         = "closeRight()"
-          class            =  "typeahead"
-          options          = "typeAheadOpts"
-          datasets         = "allAdresses"
-          ng-keypress      = "($event.which === 13) ? onSubmitAddress():0"
+          type                 = "text"
+          placeholder          = "Pour tester votre éligibilité, merci de saisir une adresse"
+          ng-model             = "address"
+          ng-focus             = "closeRight()"
+          class                = "typeahead"
+          options              = "typeAheadOpts"
+          datasets             = "allAdresses"
+          typeahead-on-select  = "onSubmitAddress($item, $model, $label)"
+          uib-typeahead        = "address for address in suggestLocation($viewValue)"
+          typeahead-loading    = "loadingLocations"
+          typeahead-no-results = "noResults"
           autofocus/>
+
+        <div
+          ng-show = "loadingLocations"
+          class   = "loading-typeahead-results">
+
+          <i
+            class   = "glyphicon glyphicon-refresh">
+          </i>
+
+          Recherche en cours...
+        </div>
+
+
+        <div
+          ng-show = "noResults"
+          class   = "no-typeahead-results">
+
+          <i class = "glyphicon glyphicon-remove"></i>
+
+          Aucun résultat trouvé!
+        </div>
+
+        <!--
+
+        <input
+          type="text"
+          class="form-control"
+          ng-model="asyncSelected"
+          placeholder="Locations loaded via $http"
+          uib-typeahead="address for address in getLocation($viewValue)"
+          typeahead-loading="loadingLocations"
+          typeahead-no-results="noResults" />
+          -->
       </div>
 
       <md-button

+ 25 - 35
js/app.js

@@ -1,5 +1,5 @@
 // file:///Users/francoisbeaufils/Downloads/geocoder-demo-master/index.html
-var app = angular.module('application', ['ngMaterial', 'adaptive.detection']);
+var app = angular.module('application', ['ngMaterial', 'adaptive.detection', 'ui.bootstrap.typeahead']);
 
 app.directive('skrollr', function() {
   var directiveDefinitionObject = {
@@ -622,39 +622,21 @@ app.controller('mainController', function($scope, $detection, $http, $mdSidenav,
 
       var suggestURL = 'https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/suggest?f=pjson&searchExtent=' + JSON.stringify(searchExtent);
 
-      $('.address-bar input').typeahead({
-        onSelect: function(item) {
-          $scope.address  = item.text;
-          var suggestion  = _.find($scope.suggestions, {text: item.text});
-          $scope.magicKey = suggestion && suggestion.magicKey;
-          $scope.$evalAsync();
-          $scope.onSubmitAddress();
-        },
-        ajax: {
-          url: suggestURL,
-          timeout: 500,
-          displayField: "text",
-          triggerLength: 1,
-          method: "get",
-          loadingClass: "loading-circle",
-          preDispatch: function (query) {
-            var q = query;
-            $('.typeahead.dropdown-menu').hide();
-            $scope.suggestions = null;
-            return {
-                text: q
-            };
-          },
-          valueField: 'suggestions',
-          preProcess: function (data) {
-            console.log('suggestions', data && data.suggestions);
-
-            $scope.suggestions = data.suggestions;
-            $scope.magicKey = null;
-            return data.suggestions;
+      $scope.suggestLocation = function(val) {
+        console.log('__suggestLocation', val);
+
+        return $http.get(suggestURL, {
+          params: {
+            text: val
           }
-        }
-      });
+        }).then(function(response){
+          $scope.suggestions = response.data.suggestions;
+          console.log('__suggestLocation response', response);
+          return response.data.suggestions.map(function(item){
+            return item.text;
+          });
+        });
+      };
     });
 
     //console.time('FTTH data');
@@ -1008,9 +990,17 @@ app.controller('mainController', function($scope, $detection, $http, $mdSidenav,
     return isValid;
   };
 
-  $scope.onSubmitAddress = function() {
-    $scope.results = null;
+  $scope.onSubmitAddress = function($item, $model, $label) {
+
+    console.log('onSubmitAddress address', $scope.address);
+    console.log('onSubmitAddress $item', $item);
+    console.log('onSubmitAddress $model', $model);
+    console.log('onSubmitAddress $label', $label);
 
+    // Get magic key
+    var item = _.find($scope.suggestions, {text: $item});
+    $scope.magicKey = item.magicKey;
+    console.log('onSubmitAddress -_-item', item, '=>', $scope.magicKey);
     $scope.getGeoCode($scope.address, $scope.magicKey, function(err, data) {
 
       $scope.geoCode = data && data.locations && data.locations[0] && data.locations[0].feature.geometry;

Fichier diff supprimé car celui-ci est trop grand
+ 0 - 9
js/bootstrap-typeahead.min.js


+ 778 - 0
js/typeahead.js

@@ -0,0 +1,778 @@
+angular.module('ui.bootstrap.debounce', [])
+/**
+ * A helper, internal service that debounces a function
+ */
+  .factory('$$debounce', ['$timeout', function($timeout) {
+    return function(callback, debounceTime) {
+      var timeoutPromise;
+
+      return function() {
+        var self = this;
+        var args = Array.prototype.slice(arguments);
+        if (timeoutPromise) {
+          $timeout.cancel(timeoutPromise);
+        }
+
+        timeoutPromise = $timeout(function() {
+          callback.apply(self, args);
+        }, debounceTime);
+      };
+    };
+  }]);
+
+  angular.module('ui.bootstrap.position', [])
+
+  /**
+   * A set of utility methods that can be use to retrieve position of DOM elements.
+   * It is meant to be used where we need to absolute-position DOM elements in
+   * relation to other, existing elements (this is the case for tooltips, popovers,
+   * typeahead suggestions etc.).
+   */
+    .factory('$uibPosition', ['$document', '$window', function($document, $window) {
+      function getStyle(el, cssprop) {
+        if (el.currentStyle) { //IE
+          return el.currentStyle[cssprop];
+        } else if ($window.getComputedStyle) {
+          return $window.getComputedStyle(el)[cssprop];
+        }
+        // finally try and get inline style
+        return el.style[cssprop];
+      }
+
+      /**
+       * Checks if a given element is statically positioned
+       * @param element - raw DOM element
+       */
+      function isStaticPositioned(element) {
+        return (getStyle(element, 'position') || 'static' ) === 'static';
+      }
+
+      /**
+       * returns the closest, non-statically positioned parentOffset of a given element
+       * @param element
+       */
+      var parentOffsetEl = function(element) {
+        var docDomEl = $document[0];
+        var offsetParent = element.offsetParent || docDomEl;
+        while (offsetParent && offsetParent !== docDomEl && isStaticPositioned(offsetParent) ) {
+          offsetParent = offsetParent.offsetParent;
+        }
+        return offsetParent || docDomEl;
+      };
+
+      return {
+        /**
+         * Provides read-only equivalent of jQuery's position function:
+         * http://api.jquery.com/position/
+         */
+        position: function(element) {
+          var elBCR = this.offset(element);
+          var offsetParentBCR = { top: 0, left: 0 };
+          var offsetParentEl = parentOffsetEl(element[0]);
+          if (offsetParentEl !== $document[0]) {
+            offsetParentBCR = this.offset(angular.element(offsetParentEl));
+            offsetParentBCR.top += offsetParentEl.clientTop - offsetParentEl.scrollTop;
+            offsetParentBCR.left += offsetParentEl.clientLeft - offsetParentEl.scrollLeft;
+          }
+
+          var boundingClientRect = element[0].getBoundingClientRect();
+          return {
+            width: boundingClientRect.width || element.prop('offsetWidth'),
+            height: boundingClientRect.height || element.prop('offsetHeight'),
+            top: elBCR.top - offsetParentBCR.top,
+            left: elBCR.left - offsetParentBCR.left
+          };
+        },
+
+        /**
+         * Provides read-only equivalent of jQuery's offset function:
+         * http://api.jquery.com/offset/
+         */
+        offset: function(element) {
+          var boundingClientRect = element[0].getBoundingClientRect();
+          return {
+            width: boundingClientRect.width || element.prop('offsetWidth'),
+            height: boundingClientRect.height || element.prop('offsetHeight'),
+            top: boundingClientRect.top + ($window.pageYOffset || $document[0].documentElement.scrollTop),
+            left: boundingClientRect.left + ($window.pageXOffset || $document[0].documentElement.scrollLeft)
+          };
+        },
+
+        /**
+         * Provides coordinates for the targetEl in relation to hostEl
+         */
+        positionElements: function(hostEl, targetEl, positionStr, appendToBody) {
+          var positionStrParts = positionStr.split('-');
+          var pos0 = positionStrParts[0], pos1 = positionStrParts[1] || 'center';
+
+          var hostElPos,
+            targetElWidth,
+            targetElHeight,
+            targetElPos;
+
+          hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl);
+
+          targetElWidth = targetEl.prop('offsetWidth');
+          targetElHeight = targetEl.prop('offsetHeight');
+
+          var shiftWidth = {
+            center: function() {
+              return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2;
+            },
+            left: function() {
+              return hostElPos.left;
+            },
+            right: function() {
+              return hostElPos.left + hostElPos.width;
+            }
+          };
+
+          var shiftHeight = {
+            center: function() {
+              return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2;
+            },
+            top: function() {
+              return hostElPos.top;
+            },
+            bottom: function() {
+              return hostElPos.top + hostElPos.height;
+            }
+          };
+
+          switch (pos0) {
+            case 'right':
+              targetElPos = {
+                top: shiftHeight[pos1](),
+                left: shiftWidth[pos0]()
+              };
+              break;
+            case 'left':
+              targetElPos = {
+                top: shiftHeight[pos1](),
+                left: hostElPos.left - targetElWidth
+              };
+              break;
+            case 'bottom':
+              targetElPos = {
+                top: shiftHeight[pos0](),
+                left: shiftWidth[pos1]()
+              };
+              break;
+            default:
+              targetElPos = {
+                top: hostElPos.top - targetElHeight,
+                left: shiftWidth[pos1]()
+              };
+          }
+
+          return targetElPos;
+        }
+      };
+    }]);
+angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap.position'])
+
+/**
+ * A helper service that can parse typeahead's syntax (string provided by users)
+ * Extracted to a separate service for ease of unit testing
+ */
+  .factory('uibTypeaheadParser', ['$parse', function($parse) {
+    //                      00000111000000000000022200000000000000003333333333333330000000000044000
+    var TYPEAHEAD_REGEXP = /^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+([\s\S]+?)$/;
+    return {
+      parse: function(input) {
+        var match = input.match(TYPEAHEAD_REGEXP);
+        if (!match) {
+          throw new Error(
+            'Expected typeahead specification in form of "_modelValue_ (as _label_)? for _item_ in _collection_"' +
+              ' but got "' + input + '".');
+        }
+
+        return {
+          itemName: match[3],
+          source: $parse(match[4]),
+          viewMapper: $parse(match[2] || match[1]),
+          modelMapper: $parse(match[1])
+        };
+      }
+    };
+  }])
+
+  .controller('UibTypeaheadController', ['$scope', '$element', '$attrs', '$compile', '$parse', '$q', '$timeout', '$document', '$window', '$rootScope', '$$debounce', '$uibPosition', 'uibTypeaheadParser',
+    function(originalScope, element, attrs, $compile, $parse, $q, $timeout, $document, $window, $rootScope, $$debounce, $position, typeaheadParser) {
+    var HOT_KEYS = [9, 13, 27, 38, 40];
+    var eventDebounceTime = 200;
+    var modelCtrl, ngModelOptions;
+    //SUPPORTED ATTRIBUTES (OPTIONS)
+
+    //minimal no of characters that needs to be entered before typeahead kicks-in
+    var minLength = originalScope.$eval(attrs.typeaheadMinLength);
+    if (!minLength && minLength !== 0) {
+      minLength = 1;
+    }
+
+    //minimal wait time after last character typed before typeahead kicks-in
+    var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0;
+
+    //should it restrict model values to the ones selected from the popup only?
+    var isEditable = originalScope.$eval(attrs.typeaheadEditable) !== false;
+    originalScope.$watch(attrs.typeaheadEditable, function (newVal) {
+      isEditable = newVal !== false;
+    });
+
+    //binding to a variable that indicates if matches are being retrieved asynchronously
+    var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop;
+
+    //a callback executed when a match is selected
+    var onSelectCallback = $parse(attrs.typeaheadOnSelect);
+
+    //should it select highlighted popup value when losing focus?
+    var isSelectOnBlur = angular.isDefined(attrs.typeaheadSelectOnBlur) ? originalScope.$eval(attrs.typeaheadSelectOnBlur) : false;
+
+    //binding to a variable that indicates if there were no results after the query is completed
+    var isNoResultsSetter = $parse(attrs.typeaheadNoResults).assign || angular.noop;
+
+    var inputFormatter = attrs.typeaheadInputFormatter ? $parse(attrs.typeaheadInputFormatter) : undefined;
+
+    var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;
+
+    var appendTo = attrs.typeaheadAppendTo ?
+      originalScope.$eval(attrs.typeaheadAppendTo) : null;
+
+    var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
+
+    //If input matches an item of the list exactly, select it automatically
+    var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;
+
+    //binding to a variable that indicates if dropdown is open
+    var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
+
+    var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
+
+    //INTERNAL VARIABLES
+
+    //model setter executed upon match selection
+    var parsedModel = $parse(attrs.ngModel);
+    var invokeModelSetter = $parse(attrs.ngModel + '($$$p)');
+    var $setModelValue = function(scope, newValue) {
+      if (angular.isFunction(parsedModel(originalScope)) &&
+        ngModelOptions && ngModelOptions.$options && ngModelOptions.$options.getterSetter) {
+        return invokeModelSetter(scope, {$$$p: newValue});
+      }
+
+      return parsedModel.assign(scope, newValue);
+    };
+
+    //expressions used by typeahead
+    var parserResult = typeaheadParser.parse(attrs.uibTypeahead);
+
+    var hasFocus;
+
+    //Used to avoid bug in iOS webview where iOS keyboard does not fire
+    //mousedown & mouseup events
+    //Issue #3699
+    var selected;
+
+    //create a child scope for the typeahead directive so we are not polluting original scope
+    //with typeahead-specific data (matches, query etc.)
+    var scope = originalScope.$new();
+    var offDestroy = originalScope.$on('$destroy', function() {
+      scope.$destroy();
+    });
+    scope.$on('$destroy', offDestroy);
+
+    // WAI-ARIA
+    var popupId = 'typeahead-' + scope.$id + '-' + Math.floor(Math.random() * 10000);
+    element.attr({
+      'aria-autocomplete': 'list',
+      'aria-expanded': false,
+      'aria-owns': popupId
+    });
+
+    var inputsContainer, hintInputElem;
+    //add read-only input to show hint
+    if (showHint) {
+      inputsContainer = angular.element('<div></div>');
+      inputsContainer.css('position', 'relative');
+      element.after(inputsContainer);
+      hintInputElem = element.clone();
+      hintInputElem.attr('placeholder', '');
+      hintInputElem.val('');
+      hintInputElem.css({
+        'position': 'absolute',
+        'top': '0px',
+        'left': '0px',
+        'border-color': 'transparent',
+        'box-shadow': 'none',
+        'opacity': 1,
+        'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
+        'color': '#999'
+      });
+      element.css({
+        'position': 'relative',
+        'vertical-align': 'top',
+        'background-color': 'transparent'
+      });
+      inputsContainer.append(hintInputElem);
+      hintInputElem.after(element);
+    }
+
+    //pop-up element used to display matches
+    var popUpEl = angular.element('<div uib-typeahead-popup></div>');
+    popUpEl.attr({
+      id: popupId,
+      matches: 'matches',
+      active: 'activeIdx',
+      select: 'select(activeIdx)',
+      'move-in-progress': 'moveInProgress',
+      query: 'query',
+      position: 'position',
+      'assign-is-open': 'assignIsOpen(isOpen)'
+    });
+    //custom item template
+    if (angular.isDefined(attrs.typeaheadTemplateUrl)) {
+      popUpEl.attr('template-url', attrs.typeaheadTemplateUrl);
+    }
+
+    if (angular.isDefined(attrs.typeaheadPopupTemplateUrl)) {
+      popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
+    }
+
+    var resetHint = function() {
+      if (showHint) {
+        hintInputElem.val('');
+      }
+    };
+
+    var resetMatches = function() {
+      scope.matches = [];
+      scope.activeIdx = -1;
+      element.attr('aria-expanded', false);
+      resetHint();
+    };
+
+    var getMatchId = function(index) {
+      return popupId + '-option-' + index;
+    };
+
+    // Indicate that the specified match is the active (pre-selected) item in the list owned by this typeahead.
+    // This attribute is added or removed automatically when the `activeIdx` changes.
+    scope.$watch('activeIdx', function(index) {
+      if (index < 0) {
+        element.removeAttr('aria-activedescendant');
+      } else {
+        element.attr('aria-activedescendant', getMatchId(index));
+      }
+    });
+
+    var inputIsExactMatch = function(inputValue, index) {
+      if (scope.matches.length > index && inputValue) {
+        return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
+      }
+
+      return false;
+    };
+
+    var getMatchesAsync = function(inputValue) {
+      var locals = {$viewValue: inputValue};
+      isLoadingSetter(originalScope, true);
+      isNoResultsSetter(originalScope, false);
+      $q.when(parserResult.source(originalScope, locals)).then(function(matches) {
+        //it might happen that several async queries were in progress if a user were typing fast
+        //but we are interested only in responses that correspond to the current view value
+        var onCurrentRequest = inputValue === modelCtrl.$viewValue;
+        if (onCurrentRequest && hasFocus) {
+          if (matches && matches.length > 0) {
+            scope.activeIdx = focusFirst ? 0 : -1;
+            isNoResultsSetter(originalScope, false);
+            scope.matches.length = 0;
+
+            //transform labels
+            for (var i = 0; i < matches.length; i++) {
+              locals[parserResult.itemName] = matches[i];
+              scope.matches.push({
+                id: getMatchId(i),
+                label: parserResult.viewMapper(scope, locals),
+                model: matches[i]
+              });
+            }
+
+            scope.query = inputValue;
+            //position pop-up with matches - we need to re-calculate its position each time we are opening a window
+            //with matches as a pop-up might be absolute-positioned and position of an input might have changed on a page
+            //due to other elements being rendered
+            recalculatePosition();
+
+            element.attr('aria-expanded', true);
+
+            //Select the single remaining option if user input matches
+            if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
+              scope.select(0);
+            }
+
+            if (showHint) {
+              var firstLabel = scope.matches[0].label;
+              if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
+                hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
+              }
+              else {
+                hintInputElem.val('');
+              }
+            }
+          } else {
+            resetMatches();
+            isNoResultsSetter(originalScope, true);
+          }
+        }
+        if (onCurrentRequest) {
+          isLoadingSetter(originalScope, false);
+        }
+      }, function() {
+        resetMatches();
+        isLoadingSetter(originalScope, false);
+        isNoResultsSetter(originalScope, true);
+      });
+    };
+
+    // bind events only if appendToBody params exist - performance feature
+    if (appendToBody) {
+      angular.element($window).bind('resize', fireRecalculating);
+      $document.find('body').bind('scroll', fireRecalculating);
+    }
+
+    // Declare the debounced function outside recalculating for
+    // proper debouncing
+    var debouncedRecalculate = $$debounce(function() {
+      // if popup is visible
+      if (scope.matches.length) {
+        recalculatePosition();
+      }
+
+      scope.moveInProgress = false;
+    }, eventDebounceTime);
+
+    // Default progress type
+    scope.moveInProgress = false;
+
+    function fireRecalculating() {
+      if (!scope.moveInProgress) {
+        scope.moveInProgress = true;
+        scope.$digest();
+      }
+
+      debouncedRecalculate();
+    }
+
+    // recalculate actual position and set new values to scope
+    // after digest loop is popup in right position
+    function recalculatePosition() {
+      scope.position = appendToBody ? $position.offset(element) : $position.position(element);
+      scope.position.top += element.prop('offsetHeight');
+    }
+
+    //we need to propagate user's query so we can higlight matches
+    scope.query = undefined;
+
+    //Declare the timeout promise var outside the function scope so that stacked calls can be cancelled later
+    var timeoutPromise;
+
+    var scheduleSearchWithTimeout = function(inputValue) {
+      timeoutPromise = $timeout(function() {
+        getMatchesAsync(inputValue);
+      }, waitTime);
+    };
+
+    var cancelPreviousTimeout = function() {
+      if (timeoutPromise) {
+        $timeout.cancel(timeoutPromise);
+      }
+    };
+
+    resetMatches();
+
+    scope.assignIsOpen = function (isOpen) {
+        isOpenSetter(originalScope, isOpen);
+    };
+
+    scope.select = function(activeIdx) {
+      //called from within the $digest() cycle
+      var locals = {};
+      var model, item;
+
+      selected = true;
+      locals[parserResult.itemName] = item = scope.matches[activeIdx].model;
+      model = parserResult.modelMapper(originalScope, locals);
+      $setModelValue(originalScope, model);
+      modelCtrl.$setValidity('editable', true);
+      modelCtrl.$setValidity('parse', true);
+
+      onSelectCallback(originalScope, {
+        $item: item,
+        $model: model,
+        $label: parserResult.viewMapper(originalScope, locals)
+      });
+
+      resetMatches();
+
+      //return focus to the input element if a match was selected via a mouse click event
+      // use timeout to avoid $rootScope:inprog error
+      if (scope.$eval(attrs.typeaheadFocusOnSelect) !== false) {
+        $timeout(function() { element[0].focus(); }, 0, false);
+      }
+    };
+
+    //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
+    element.bind('keydown', function(evt) {
+      //typeahead is open and an "interesting" key was pressed
+      if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
+        return;
+      }
+
+      // if there's nothing selected (i.e. focusFirst) and enter or tab is hit, clear the results
+      if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13)) {
+        resetMatches();
+        scope.$digest();
+        return;
+      }
+
+      evt.preventDefault();
+
+      if (evt.which === 40) {
+        scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
+        scope.$digest();
+        popUpEl.children()[scope.activeIdx].scrollIntoView(false);
+      } else if (evt.which === 38) {
+        scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
+        scope.$digest();
+        popUpEl.children()[scope.activeIdx].scrollIntoView(false);
+      } else if (evt.which === 13 || evt.which === 9) {
+        scope.$apply(function () {
+          scope.select(scope.activeIdx);
+        });
+      } else if (evt.which === 27) {
+        evt.stopPropagation();
+
+        resetMatches();
+        scope.$digest();
+      }
+    });
+
+    element.bind('focus', function () {
+      hasFocus = true;
+      if (minLength === 0 && !modelCtrl.$viewValue) {
+        getMatchesAsync(modelCtrl.$viewValue);
+      }
+    });
+
+    element.bind('blur', function() {
+      if (isSelectOnBlur && scope.matches.length && scope.activeIdx !== -1 && !selected) {
+        selected = true;
+        scope.$apply(function() {
+          scope.select(scope.activeIdx);
+        });
+      }
+      if (!isEditable && modelCtrl.$error.editable) {
+        modelCtrl.$viewValue = '';
+        element.val('');
+      }
+      hasFocus = false;
+      selected = false;
+    });
+
+    // Keep reference to click handler to unbind it.
+    var dismissClickHandler = function(evt) {
+      // Issue #3973
+      // Firefox treats right click as a click on document
+      if (element[0] !== evt.target && evt.which !== 3 && scope.matches.length !== 0) {
+        resetMatches();
+        if (!$rootScope.$$phase) {
+          scope.$digest();
+        }
+      }
+    };
+
+    $document.bind('click', dismissClickHandler);
+
+    originalScope.$on('$destroy', function() {
+      $document.unbind('click', dismissClickHandler);
+      if (appendToBody || appendTo) {
+        $popup.remove();
+      }
+
+      if (appendToBody) {
+        angular.element($window).unbind('resize', fireRecalculating);
+        $document.find('body').unbind('scroll', fireRecalculating);
+      }
+      // Prevent jQuery cache memory leak
+      popUpEl.remove();
+
+      if (showHint) {
+          inputsContainer.remove();
+      }
+    });
+
+    var $popup = $compile(popUpEl)(scope);
+
+    if (appendToBody) {
+      $document.find('body').append($popup);
+    } else if (appendTo) {
+      angular.element(appendTo).eq(0).append($popup);
+    } else {
+      element.after($popup);
+    }
+
+    this.init = function(_modelCtrl, _ngModelOptions) {
+      modelCtrl = _modelCtrl;
+      ngModelOptions = _ngModelOptions;
+
+      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
+      //$parsers kick-in on all the changes coming from the view as well as manually triggered by $setViewValue
+      modelCtrl.$parsers.unshift(function(inputValue) {
+        hasFocus = true;
+
+        if (minLength === 0 || inputValue && inputValue.length >= minLength) {
+          if (waitTime > 0) {
+            cancelPreviousTimeout();
+            scheduleSearchWithTimeout(inputValue);
+          } else {
+            getMatchesAsync(inputValue);
+          }
+        } else {
+          isLoadingSetter(originalScope, false);
+          cancelPreviousTimeout();
+          resetMatches();
+        }
+
+        if (isEditable) {
+          return inputValue;
+        }
+
+        if (!inputValue) {
+          // Reset in case user had typed something previously.
+          modelCtrl.$setValidity('editable', true);
+          return null;
+        }
+
+        modelCtrl.$setValidity('editable', false);
+        return undefined;
+      });
+
+      modelCtrl.$formatters.push(function(modelValue) {
+        var candidateViewValue, emptyViewValue;
+        var locals = {};
+
+        // The validity may be set to false via $parsers (see above) if
+        // the model is restricted to selected values. If the model
+        // is set manually it is considered to be valid.
+        if (!isEditable) {
+          modelCtrl.$setValidity('editable', true);
+        }
+
+        if (inputFormatter) {
+          locals.$model = modelValue;
+          return inputFormatter(originalScope, locals);
+        }
+
+        //it might happen that we don't have enough info to properly render input value
+        //we need to check for this situation and simply return model value if we can't apply custom formatting
+        locals[parserResult.itemName] = modelValue;
+        candidateViewValue = parserResult.viewMapper(originalScope, locals);
+        locals[parserResult.itemName] = undefined;
+        emptyViewValue = parserResult.viewMapper(originalScope, locals);
+
+        return candidateViewValue !== emptyViewValue ? candidateViewValue : modelValue;
+      });
+    };
+  }])
+
+  .directive('uibTypeahead', function() {
+    return {
+      controller: 'UibTypeaheadController',
+      require: ['ngModel', '^?ngModelOptions', 'uibTypeahead'],
+      link: function(originalScope, element, attrs, ctrls) {
+        ctrls[2].init(ctrls[0], ctrls[1]);
+      }
+    };
+  })
+
+  .directive('uibTypeaheadPopup', function() {
+    return {
+      scope: {
+        matches: '=',
+        query: '=',
+        active: '=',
+        position: '&',
+        moveInProgress: '=',
+        select: '&',
+        assignIsOpen: '&'
+      },
+      replace: true,
+      templateUrl: function(element, attrs) {
+        return attrs.popupTemplateUrl || 'html/typeahead-popup.html';
+      },
+      link: function(scope, element, attrs) {
+        scope.templateUrl = attrs.templateUrl;
+
+        scope.isOpen = function() {
+          var isDropdownOpen = scope.matches.length > 0;
+          scope.assignIsOpen({ isOpen: isDropdownOpen });
+          return isDropdownOpen;
+        };
+
+        scope.isActive = function(matchIdx) {
+          return scope.active === matchIdx;
+        };
+
+        scope.selectActive = function(matchIdx) {
+          scope.active = matchIdx;
+        };
+
+        scope.selectMatch = function(activeIdx) {
+          scope.select({activeIdx: activeIdx});
+        };
+      }
+    };
+  })
+
+  .directive('uibTypeaheadMatch', ['$templateRequest', '$compile', '$parse', function($templateRequest, $compile, $parse) {
+    return {
+      scope: {
+        index: '=',
+        match: '=',
+        query: '='
+      },
+      link: function(scope, element, attrs) {
+        var tplUrl = $parse(attrs.templateUrl)(scope.$parent) || 'html/typeahead-match.html';
+        $templateRequest(tplUrl).then(function(tplContent) {
+          var tplEl = angular.element(tplContent.trim());
+          element.replaceWith(tplEl);
+          $compile(tplEl)(scope);
+        });
+      }
+    };
+  }])
+
+  .filter('uibTypeaheadHighlight', ['$sce', '$injector', '$log', function($sce, $injector, $log) {
+    var isSanitizePresent;
+    isSanitizePresent = $injector.has('$sanitize');
+
+    function escapeRegexp(queryToEscape) {
+      // Regex: capture the whole query string and replace it with the string that will be used to match
+      // the results, for example if the capture is "a" the result will be \a
+      return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
+    }
+
+    function containsHtml(matchItem) {
+      return /<.*>/g.test(matchItem);
+    }
+
+    return function(matchItem, query) {
+      if (!isSanitizePresent && containsHtml(matchItem)) {
+        $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
+      }
+      matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'gi'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag
+      if (!isSanitizePresent) {
+        matchItem = $sce.trustAsHtml(matchItem); // If $sanitize is not present we pack the string in a $sce object for the ng-bind-html directive
+      }
+      return matchItem;
+    };
+  }]);

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff