2
votes

I am using UI bootstrap type-ahead and I am matching based on the leading characters. For example if I type 'A' in the input box I would like to see all the states that start with 'A' as in "Alabama" and not all the states that contain an 'A' in the name.

Now what I want to achieve is if I type 'A' in the input box in addition to filtering out all states that start with 'A', I want only the first A to be highlighted. If the word is "Alabama" it should highlight only the first "A" of "Alabama" and not other occurences of "A". If I have the word 'Papa' in my list and I type 'Pa' in the input box it should highlight the first occurrence of the word 'Pa'in 'Papa'.

Here is my sample code

myHtml.html

<div class='container-fluid' ng-controller="TypeaheadCtrl">
   <input type="text" ng-model="selected" typeahead="state for state in states | filter:$viewValue:startsWith">
</div>

myJs.js

angular.module('ui.bootstrap.demo').controller('TypeaheadCtrl',   function($scope) {

  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas'];

 $scope.startsWith = function(state, viewValue) {
        return state.substr(0, viewValue.length).toLowerCase() ==  viewValue.toLowerCase();
      } 
 });
1

1 Answers

0
votes

There are two (or three maybe) options, depending on whether or not you're using a custom results template.

decorate the existing filter

If using the built-in results template, you can override or decorate the existing filter used. Like so:

(function () {
    'use strict';

    angular.module('yourModuleName')
        .config(['$provide', '$injector', function($provide, $injector) {

            $provide.decorator('uibTypeaheadHighlightFilter', ['$delegate', function($delegate) {

                var extendsFilter = function() {
                    var 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(/([.?*+^$[\]\\(){}|-])/, '\\$1');
                    }

                    function containsHtml(matchItem) {
                        return /<.*>/g.test(matchItem);
                    }

                    function matchFunction (matchItem, query) {
                        if (!isSanitizePresent && containsHtml(matchItem)) {
                            $log.warn('Unsafe use of typeahead please use ngSanitize'); // Warn the user about the danger
                        }
                        
/* THIS LINE HERE IS THE ONLY LINE I CHANGED */                        
                        matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'i'), '<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;
                    }

                    return matchFunction.apply(null, arguments);
                };

                return extendsFilter;
            }]);
        }]);
})();

Notice that the only change i made to the original filter source code was in the regex replace, right under where i marked /* THIS LINE HERE IS THE ONLY LINE I CHANGED */

original source here, at the very bottom: https://github.com/angular-ui/bootstrap/blob/master/src/typeahead/typeahead.js

Old Code:

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

New Code (removed the regex global flag 'g'):

matchItem = query ? ('' + matchItem).replace(new RegExp(escapeRegexp(query), 'i'), '<strong>$&</strong>') : matchItem; // Replaces the capture string with a the same string inside of a "strong" tag

Use your own custom filter

If you are using a custom results template ( see examples' source code http://angular-ui.github.io/bootstrap/versioned-docs/2.4.0/#/typeahead ), you can just specify your own highlight filter in the custom results template, like so:

<script type="text/ng-template" id="yourCustomResultsTemplate.html">
  <a>
      <img ng-src="http://upload.wikimedia.org/wikipedia/commons/thumb/{{match.model.flag}}" width="16">
      <span ng-bind-html="match.label | uibTypeaheadHighlightCustomizedByYou: query"></span>
  </a>
</script>

Then you would just copy/paste the original filter source code, and modify it in the same way as we did above.