/* --- Made by justgoscha and licensed under MIT license --- */ var app = angular.module('autocomplete', []); app.directive('autocomplete', function() { var index = -1; return { restrict: 'E', scope: { searchParam: '=ngModel', suggestions: '=data', onType: '=onType', onSelect: '=onSelect' }, controller: ['$scope', function($scope){ // the index of the suggestions that's currently selected $scope.selectedIndex = -1; // set new index $scope.setIndex = function(i){ $scope.selectedIndex = parseInt(i); }; this.setIndex = function(i){ $scope.setIndex(i); $scope.$apply(); }; $scope.getIndex = function(i){ return $scope.selectedIndex; }; // watches if the parameter filter should be changed var watching = true; // autocompleting drop down on/off $scope.completing = false; // starts autocompleting on typing in something $scope.$watch('searchParam', function(newValue, oldValue){ if (oldValue === newValue) { return; } if(watching && $scope.searchParam) { $scope.completing = true; $scope.searchFilter = $scope.searchParam; $scope.selectedIndex = -1; } // function thats passed to on-type attribute gets executed if($scope.onType) $scope.onType($scope.searchParam); }); // for hovering over suggestions this.preSelect = function(suggestion){ watching = false; // this line determines if it is shown // in the input field before it's selected: //$scope.searchParam = suggestion; $scope.$apply(); watching = true; }; $scope.preSelect = this.preSelect; this.preSelectOff = function(){ watching = true; }; $scope.preSelectOff = this.preSelectOff; // selecting a suggestion with RIGHT ARROW or ENTER $scope.select = function(suggestion){ if(suggestion){ $scope.searchParam = suggestion; $scope.searchFilter = suggestion; if($scope.onSelect) $scope.onSelect(suggestion); } watching = false; $scope.completing = false; setTimeout(function(){watching = true;},1000); $scope.setIndex(-1); }; }], link: function(scope, element, attrs){ var attr = ''; // Default atts scope.attrs = { "placeholder": "start typing...", "class": "", "id": "", "inputclass": "", "inputid": "" }; for (var a in attrs) { attr = a.replace('attr', '').toLowerCase(); // add attribute overriding defaults // and preventing duplication if (a.indexOf('attr') === 0) { scope.attrs[attr] = attrs[a]; } } if (attrs.clickActivation) { element[0].onclick = function(e){ if(!scope.searchParam){ scope.completing = true; scope.$apply(); } }; } var key = {left: 37, up: 38, right: 39, down: 40 , enter: 13, esc: 27}; document.addEventListener("keydown", function(e){ var keycode = e.keyCode || e.which; switch (keycode){ case key.esc: // disable suggestions on escape scope.select(); scope.setIndex(-1); scope.$apply(); e.preventDefault(); } }, true); document.addEventListener("blur", function(e){ // disable suggestions on blur // we do a timeout to prevent hiding it before a click event is registered setTimeout(function() { scope.select(); scope.setIndex(-1); scope.$apply(); }, 200); }, true); element[0].addEventListener("keydown",function (e){ var keycode = e.keyCode || e.which; var l = angular.element(this).find('li').length; // implementation of the up and down movement in the list of suggestions switch (keycode){ case key.up: index = scope.getIndex()-1; if(index<-1){ index = l-1; } else if (index >= l ){ index = -1; scope.setIndex(index); scope.preSelectOff(); break; } scope.setIndex(index); if(index!==-1) scope.preSelect(angular.element(angular.element(this).find('li')[index]).text()); scope.$apply(); break; case key.down: index = scope.getIndex()+1; if(index<-1){ index = l-1; } else if (index >= l ){ index = -1; scope.setIndex(index); scope.preSelectOff(); scope.$apply(); break; } scope.setIndex(index); if(index!==-1) scope.preSelect(angular.element(angular.element(this).find('li')[index]).text()); break; case key.left: break; case key.right: case key.enter: index = scope.getIndex(); // scope.preSelectOff(); if(index !== -1) scope.select(angular.element(angular.element(this).find('li')[index]).text()); scope.setIndex(-1); scope.$apply(); break; case key.esc: // disable suggestions on escape scope.select(); scope.setIndex(-1); scope.$apply(); e.preventDefault(); break; default: return; } if(scope.getIndex()!==-1 || keycode == key.enter) e.preventDefault(); }); }, templateUrl: 'js/libs/angular-autocomplete/ac_template.html' }; }); app.filter('highlight', ['$sce', function ($sce) { return function (input, searchParam) { if (typeof input === 'function') return ''; if (searchParam) { var words = '(' + searchParam.split(/\ /).join(' |') + '|' + searchParam.split(/\ /).join('|') + ')', exp = new RegExp(words, 'gi'); if (words.length) { input = input.replace(exp, "$1"); } } return $sce.trustAsHtml(input); }; }]); app.directive('suggestion', function(){ return { restrict: 'A', require: '^autocomplete', // ^look for controller on parents element link: function(scope, element, attrs, autoCtrl){ element.bind('mouseenter', function() { autoCtrl.preSelect(attrs.val); autoCtrl.setIndex(attrs.index); }); element.bind('mouseleave', function() { autoCtrl.preSelectOff(); }); } }; });