Search code examples
javascriptangularjsangularjs-directiveangularjs-ng-repeatangular-filters

How to get value from ng-repeat when event not from element within ng-repeat (i.e. not ng-click)


I have a lookahead fuzzy search directive for category tagging:

<input type="text" class="form-control" placeholder="Apply Tags" ng-keydown="checkKeyDown($event)" ng-change="searchTags()" ng-model="searchText">
<div ng-show="searchText.length>0">
    <ul>
        <li ng-repeat="tag in suggestedTags | fuzzySearch : searchText : 'OR' track by $index" ng-class="{active : $index === selectedIndex}">
            {{tag}}
        </li>
    </ul>
</div>

This is how I'm handling things

suggestedTags: array of strings that is the full list of tags I want to match
selectedIndex: the index of the filtered suggestedTags, based on searchText
searchText: the string I'm using to match up with suggestedTags

When I type something, the fuzzy search checks to see if what I typed matches anything into an array, and shortens the list of suggested results. This works fine.

However, I have the following code which allows me to use the keyboard to select from the filtered list (using up, down, and enter keys):

scope.checkKeyDown = function(event) {
  if (event.keyCode === 40) { //down key, increment selectedIndex
    event.preventDefault();
    if(scope.selectedIndex+1 !== scope.suggestedTags.length){ // check to see if at end of list
      scope.selectedIndex++;
    }
  } else if (event.keyCode === 38) { //up key, decrement selectedIndex
    event.preventDefault();
    if(scope.selectedIndex-1 !== -1){ // check to see if at top of list
      scope.selectedIndex--;
    }
  } else if (event.keyCode === 13) { //enter pressed
    event.preventDefault();
    scope.addToSelectedTags(scope.selectedIndex); //adds to selected tags
  }
}
scope.addToSelectedTags = function (index) {
  if(scope.selectedTags.indexOf(scope.suggestedTags[index]) > -1) return; // Test to see if selectedTags already has this value
    scope.selectedTags.push(scope.suggestedTags[index]);
};

The problem here is that selectedIndex will be the index from the filtered list, but when I push to selectedTags, it's going to be from the full suggestedTags array.

The easiest way to do this is just passing in the string value from whatever is selected. How can I do this? Is there a way to put ng-model on the <li> (which repeats), and have it select whatever is active?


Solution

  • function Ctrl($scope, $filter) {
      var suggestedTags = ['javascript', 'angular', 'android', 'java', 'c++', 'c#', 'object-c']
      $scope.selectedIndex = 0;
      $scope.filteredTags = suggestedTags;
      $scope.selectedTags = [];
      $scope.checkKeyDown = function(event) {
        if (event.keyCode === 40) { //down key, increment selectedIndex
          console.log('down');
          event.preventDefault();
          if ($scope.selectedIndex + 1 !== $scope.filteredTags.length) { // check to see if at end of list
            $scope.selectedIndex++;
          }
        } else if (event.keyCode === 38) { //up key, decrement selectedIndex
          console.log('up')
          event.preventDefault();
          if ($scope.selectedIndex - 1 !== -1) { // check to see if at top of list
            $scope.selectedIndex--;
          }
        } else if (event.keyCode === 13) { //enter pressed
          event.preventDefault();
    
          $scope.addToSelectedTags($scope.selectedIndex); //adds to selected tags
          console.log('enter');
        }
      }
    
      $scope.searchTags = function() {
        $scope.selectedIndex = 0;
        $scope.filteredTags = $filter('filter')(suggestedTags, $scope.q);
      }
    
      $scope.addToSelectedTags = function(index) {
        if ($scope.filteredTags.length - 1 >= index) {
          var selectedTag = $scope.filteredTags[index];
          //angular doesnt like duplicate in ng-repeat
          //if you want to have duplicate tag name, you can use track by 
          //or create new object so angular doesnt see it as duplicated
    
          if ($scope.selectedTags.indexOf(selectedTag) < 0)
            $scope.selectedTags.push(selectedTag);
        }
    
      }
    }
    .active {
      background-color: red;
    }
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script>
    <div ng-app ng-controller="Ctrl">
      <input tpye="text" ng-model="q" ng-keydown="checkKeyDown($event)" ng-change="searchTags()" />
      <ul>
        <li ng-repeat="tag in filteredTags" ng-class="{active : $index === selectedIndex}">{{tag}}</li>
      </ul>
    
      <ul>
        <li ng-repeat="t in selectedTags">{{t}}</li>
      </ul>
    </div>