angular-autocomplete.js 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. /* --- Made by justgoscha and licensed under MIT license --- */
  2. var app = angular.module('autocomplete', []);
  3. app.directive('autocomplete', function() {
  4. var index = -1;
  5. return {
  6. restrict: 'E',
  7. scope: {
  8. searchParam: '=ngModel',
  9. suggestions: '=data',
  10. onType: '=onType',
  11. onSelect: '=onSelect'
  12. },
  13. controller: ['$scope', function($scope){
  14. // the index of the suggestions that's currently selected
  15. $scope.selectedIndex = -1;
  16. // set new index
  17. $scope.setIndex = function(i){
  18. $scope.selectedIndex = parseInt(i);
  19. };
  20. this.setIndex = function(i){
  21. $scope.setIndex(i);
  22. $scope.$apply();
  23. };
  24. $scope.getIndex = function(i){
  25. return $scope.selectedIndex;
  26. };
  27. // watches if the parameter filter should be changed
  28. var watching = true;
  29. // autocompleting drop down on/off
  30. $scope.completing = false;
  31. // starts autocompleting on typing in something
  32. $scope.$watch('searchParam', function(newValue, oldValue){
  33. if (oldValue === newValue) {
  34. return;
  35. }
  36. if(watching && $scope.searchParam) {
  37. $scope.completing = true;
  38. $scope.searchFilter = $scope.searchParam;
  39. $scope.selectedIndex = -1;
  40. }
  41. // function thats passed to on-type attribute gets executed
  42. if($scope.onType)
  43. $scope.onType($scope.searchParam);
  44. });
  45. // for hovering over suggestions
  46. this.preSelect = function(suggestion){
  47. watching = false;
  48. // this line determines if it is shown
  49. // in the input field before it's selected:
  50. //$scope.searchParam = suggestion;
  51. $scope.$apply();
  52. watching = true;
  53. };
  54. $scope.preSelect = this.preSelect;
  55. this.preSelectOff = function(){
  56. watching = true;
  57. };
  58. $scope.preSelectOff = this.preSelectOff;
  59. // selecting a suggestion with RIGHT ARROW or ENTER
  60. $scope.select = function(suggestion){
  61. if(suggestion){
  62. $scope.searchParam = suggestion;
  63. $scope.searchFilter = suggestion;
  64. if($scope.onSelect)
  65. $scope.onSelect(suggestion);
  66. }
  67. watching = false;
  68. $scope.completing = false;
  69. setTimeout(function(){watching = true;},1000);
  70. $scope.setIndex(-1);
  71. };
  72. }],
  73. link: function(scope, element, attrs){
  74. var attr = '';
  75. // Default atts
  76. scope.attrs = {
  77. "placeholder": "start typing...",
  78. "class": "",
  79. "id": "",
  80. "inputclass": "",
  81. "inputid": ""
  82. };
  83. for (var a in attrs) {
  84. attr = a.replace('attr', '').toLowerCase();
  85. // add attribute overriding defaults
  86. // and preventing duplication
  87. if (a.indexOf('attr') === 0) {
  88. scope.attrs[attr] = attrs[a];
  89. }
  90. }
  91. if (attrs.clickActivation) {
  92. element[0].onclick = function(e){
  93. if(!scope.searchParam){
  94. scope.completing = true;
  95. scope.$apply();
  96. }
  97. };
  98. }
  99. var key = {left: 37, up: 38, right: 39, down: 40 , enter: 13, esc: 27};
  100. document.addEventListener("keydown", function(e){
  101. var keycode = e.keyCode || e.which;
  102. switch (keycode){
  103. case key.esc:
  104. // disable suggestions on escape
  105. scope.select();
  106. scope.setIndex(-1);
  107. scope.$apply();
  108. e.preventDefault();
  109. }
  110. }, true);
  111. document.addEventListener("blur", function(e){
  112. // disable suggestions on blur
  113. // we do a timeout to prevent hiding it before a click event is registered
  114. setTimeout(function() {
  115. scope.select();
  116. scope.setIndex(-1);
  117. scope.$apply();
  118. }, 200);
  119. }, true);
  120. element[0].addEventListener("keydown",function (e){
  121. var keycode = e.keyCode || e.which;
  122. var l = angular.element(this).find('li').length;
  123. // implementation of the up and down movement in the list of suggestions
  124. switch (keycode){
  125. case key.up:
  126. index = scope.getIndex()-1;
  127. if(index<-1){
  128. index = l-1;
  129. } else if (index >= l ){
  130. index = -1;
  131. scope.setIndex(index);
  132. scope.preSelectOff();
  133. break;
  134. }
  135. scope.setIndex(index);
  136. if(index!==-1)
  137. scope.preSelect(angular.element(angular.element(this).find('li')[index]).text());
  138. scope.$apply();
  139. break;
  140. case key.down:
  141. index = scope.getIndex()+1;
  142. if(index<-1){
  143. index = l-1;
  144. } else if (index >= l ){
  145. index = -1;
  146. scope.setIndex(index);
  147. scope.preSelectOff();
  148. scope.$apply();
  149. break;
  150. }
  151. scope.setIndex(index);
  152. if(index!==-1)
  153. scope.preSelect(angular.element(angular.element(this).find('li')[index]).text());
  154. break;
  155. case key.left:
  156. break;
  157. case key.right:
  158. case key.enter:
  159. index = scope.getIndex();
  160. // scope.preSelectOff();
  161. if(index !== -1)
  162. scope.select(angular.element(angular.element(this).find('li')[index]).text());
  163. scope.setIndex(-1);
  164. scope.$apply();
  165. break;
  166. case key.esc:
  167. // disable suggestions on escape
  168. scope.select();
  169. scope.setIndex(-1);
  170. scope.$apply();
  171. e.preventDefault();
  172. break;
  173. default:
  174. return;
  175. }
  176. if(scope.getIndex()!==-1 || keycode == key.enter)
  177. e.preventDefault();
  178. });
  179. },
  180. templateUrl: 'js/libs/angular-autocomplete/ac_template.html'
  181. };
  182. });
  183. app.filter('highlight', ['$sce', function ($sce) {
  184. return function (input, searchParam) {
  185. if (typeof input === 'function') return '';
  186. if (searchParam) {
  187. var words = '(' +
  188. searchParam.split(/\ /).join(' |') + '|' +
  189. searchParam.split(/\ /).join('|') +
  190. ')',
  191. exp = new RegExp(words, 'gi');
  192. if (words.length) {
  193. input = input.replace(exp, "<span class=\"highlight\">$1</span>");
  194. }
  195. }
  196. return $sce.trustAsHtml(input);
  197. };
  198. }]);
  199. app.directive('suggestion', function(){
  200. return {
  201. restrict: 'A',
  202. require: '^autocomplete', // ^look for controller on parents element
  203. link: function(scope, element, attrs, autoCtrl){
  204. element.bind('mouseenter', function() {
  205. autoCtrl.preSelect(attrs.val);
  206. autoCtrl.setIndex(attrs.index);
  207. });
  208. element.bind('mouseleave', function() {
  209. autoCtrl.preSelectOff();
  210. });
  211. }
  212. };
  213. });