35

I am using Angular JS - ui.bootstrap.typeahead:

I would like to click a button and focus an input field and automatically show the typeahead suggestion dropdown. I have a directive that automatically focuses the input field when the button is clicked. How can I show the dropdown automatically so the user can use the down arrow, or click, to quickly choose a user?

I have created a Plunker with the ui-bootstrap JS file editable for tinkering:

http://plnkr.co/edit/Z79LY0OYlwFc3wirjxol?p=preview

This is my full script:

<!doctype html>
<html ng-app="plunker">
  <head>
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
    <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.2/angular.js"></script>
    <script src="ui-bootstrap-tpls-0.10.0.js"></script>
  </head>
  <body>

<script>
  angular.module('plunker', ['ui.bootstrap'])
  .directive('focusMe', function($timeout, $parse) {
    return {
        //scope: true,   // optionally create a child scope
        link: function(scope, element, attrs) {
            var model = $parse(attrs.focusMe);
            scope.$watch(model, function(value) {
                if(value === true) { 
                    $timeout(function() {
                        element[0].focus(); 
                    });
                }
            });

        }
    };
});
function TypeaheadCtrl($scope, $http) {

  $scope.selected = undefined;
  $scope.states = ['Alabama', 'Alaska', 'Arizona', 'Arkansas', 'California', 'Colorado', 'Connecticut', 'Delaware', 'Florida', 'Georgia', 'Hawaii', 'Idaho', 'Illinois', 'Indiana', 'Iowa', 'Kansas', 'Kentucky', 'Louisiana', 'Maine', 'Maryland', 'Massachusetts', 'Michigan', 'Minnesota', 'Mississippi', 'Missouri', 'Montana', 'Nebraska', 'Nevada', 'New Hampshire', 'New Jersey', 'New Mexico', 'New York', 'North Dakota', 'North Carolina', 'Ohio', 'Oklahoma', 'Oregon', 'Pennsylvania', 'Rhode Island', 'South Carolina', 'South Dakota', 'Tennessee', 'Texas', 'Utah', 'Vermont', 'Virginia', 'Washington', 'West Virginia', 'Wisconsin', 'Wyoming'];
  $scope.opened = false;

  $scope.open = function() {
    $scope.opened = true;
  }
  $scope.close = function() {
    $scope.opened = false;
  }
}

</script>
<div class='container-fluid' ng-controller="TypeaheadCtrl">

    <h4>How can I open the typeahead dropdown automatically when button is pressed?</h4>
    <p>I have a directive that automatically focuses on the field but I can't seem to automatically show the typeahead. Even adding down arrow key click support would be great.

    <br/><br/>

    <button class="btn btn-default" ng-show="!opened" ng-click="open()">Open Input and show typeahead!</button>
    <button class="btn btn-default" ng-show="opened" ng-click="close()">Close Input</button>
    <br/><br/>

    <input type="text"
    focus-me="opened"
    ng-show="opened"
    ng-model="selected" 
    typeahead="state for state in states | filter:$viewValue | limitTo:8" 
    class="form-control">


    <br/>
    <pre ng-show="opened">Model: {{selected | json}}</pre>


</div>
  </body>
</html>
Holland Risley
  • 6,969
  • 9
  • 25
  • 34
  • that is a feature request asking this to be a standard feature... and the solution suggested was to have typeahead-min-length="0" to enable it, not able to find the exact link right now, but just search for angular ui issue and you will mostly get it.. [link](https://github.com/angular-ui/bootstrap/pull/1624) – harishr Jul 15 '14 at 18:58
  • FWIW, I came across this question because I was trying to do a focus-on-show on an input as well, but inside of an bootstrap-ui dropdown instead of just vanilla ng-show -- My input would get focused about 50% of the time that I clicked the dropdown toggle because bootstrap-dropdown hadn't finished rendering the dropdown. So I found a solution long-polling jQuery's .is(':visible') (not available in angular.element, so you have to add jQuery). I forked your plunker and added the bit of code that makes it work 100% of the time: http://plnkr.co/edit/CAIWksiNdQnVqkn8mEgW?p=preview – RavenHursT May 29 '15 at 03:57
  • I posted an answer [**here**](http://stackoverflow.com/questions/31564461/angularjs-show-typeahead-on-button-click/31568886#31568886) that you may find accommodating. – WebWanderer Jul 22 '15 at 16:38

11 Answers11

56

Updated:

I added the directive to github for easy updates and access. You can now install it as a dependency through bower.

Original post:

I came up with a pretty clean hack that does not require any changes to ui-bootstrap-tpls. The Idea is to use $setViewValue() to trigger the popup with a combination of a special filter comparator function.

In order to bypass the minLength check, $setViewValue() has to be set to a value longer than 1 so i'm using one space string. The role of the comparator function is to treat one space as a match to all items so they'll all show up when clicking on an empty input.

I created a simple directive:

angular.module('app')
.directive('typeaheadFocus', function () {
  return {
    require: 'ngModel',
    link: function (scope, element, attr, ngModel) {

      //trigger the popup on 'click' because 'focus'
      //is also triggered after the item selection
      element.bind('click', function () {

        var viewValue = ngModel.$viewValue;

        //restore to null value so that the typeahead can detect a change
        if (ngModel.$viewValue == ' ') {
          ngModel.$setViewValue(null);
        }

        //force trigger the popup
        ngModel.$setViewValue(' ');

        //set the actual value in case there was already a value in the input
        ngModel.$setViewValue(viewValue || ' ');
      });

      //compare function that treats the empty space as a match
      scope.emptyOrMatch = function (actual, expected) {
        if (expected == ' ') {
          return true;
        }
        return actual.indexOf(expected) > -1;
      };
    }
  };
});

Usage:

<input type="text" ng-model="selected" typeahead="item for item in items | filter:$viewValue:emptyOrMatch | limitTo:8" typeahead-focus >
Community
  • 1
  • 1
yohairosen
  • 1,665
  • 1
  • 18
  • 26
  • 3
    @unTarm 's solution doesn't work anymore with Angular 1.3+. But this worked. Thanks :) – ajaybc Mar 24 '15 at 05:10
  • 1
    Nice one. Even work with latest ang js. But the problem that I see is, this solution won't work when you empty the input by pressing backspace. @runTram solution "used to"(as his solution does not work with 1.3x) show dropdown when you empty the input box. – Ritesh Kumar Gupta May 13 '15 at 18:03
  • Thanks. If you haven't thought of it yet, a simple demo on your github page showing how it works would be cool. – ayjay Aug 14 '15 at 23:08
  • @yohairosen, how do I add typeahead-focus for the next input element? Do I just add `typeahead-focus` for each one of them and how can I establish their order? – ayjay Aug 14 '15 at 23:53
  • @ayjay, there's absolutely no problem using `typeahead-focus` on multiple input elements if that's your issue. Remeber that you have to use the `emptyOrMatch` filter on them. Take a look at the *Usage* section above. And thanks, I'll add a demo soon. – yohairosen Aug 15 '15 at 10:54
  • I tried https://github.com/yohairosen/typeaheadFocus and it's otherwise a good solution, but after enabling it I'm no longer able to move to the next input with tab key. It just focuses the current input again and then shows the dropdown again. Also tried setting `typeahead-focus-on-select` to false but didn't make any difference. I'm using UI bootstrap v0.14.3. – Cvuorinen Nov 30 '15 at 15:16
  • @Cvuorinen try adding the keyCode 9 to ARROW_KEYS (or create another array for control keys and return if those are hit) – yvesmancera Jan 12 '16 at 00:57
  • Thanks for the suggestion @yvesmancera, although I did already fix it and submitted a pull request https://github.com/yohairosen/typeaheadFocus/pull/8 – Cvuorinen Jan 31 '16 at 10:54
  • 2
    I added a `typeahead-min-length="0"` in parallel to this, and replaced the last `$setViewValue` to set `viewValue || ''` (e.g. 'or empty string'). That allows adding ` | filter:$viewValue` to the typeahead without the space to trigger to list of values to interfere with the filtering.. – grtjn Mar 03 '16 at 16:23
  • Works like a charm! Thank you. – John Mar 22 '17 at 08:35
  • 1
    I changed the script to be case insensitive to : return ('' + actual).toLowerCase().indexOf(('' + expected).toLowerCase()) > -1; – Kevin.Debeil May 23 '17 at 07:56
  • focus worked but unable to type and search anymore...any suggestion please? – Muhammad Ahmar Khan Mar 03 '21 at 18:17
21

As HarishR mentioned in a comment, there is no built-in support for this feature yet.

But I just want to try hacking around and here is the result: http://plnkr.co/edit/Qrnat8yTvISuM1qHHDlA?p=preview

It contains a lot of hacks to make it works:

  1. include jQuery in order to use .trigger(), could be replace with native JS but I'm lazy.
  2. use ng-focus to call .trigger('input') for triggering the typehead popup
  3. use ng-trim="false" to disable input's value auto trimming
  4. a custom empty-typeahead directive that interact with the ngModel's controller for applying the secretEmptyKey logic to bypass typeahead-min-length check:

    .directive('emptyTypeahead', function () {
      return {
        require: 'ngModel',
        link: function (scope, element, attrs, modelCtrl) {
          // this parser run before typeahead's parser
          modelCtrl.$parsers.unshift(function (inputValue) {
            var value = (inputValue ? inputValue : secretEmptyKey); // replace empty string with secretEmptyKey to bypass typeahead-min-length check
            modelCtrl.$viewValue = value; // this $viewValue must match the inputValue pass to typehead directive
            return value;
          });
    
          // this parser run after typeahead's parser
          modelCtrl.$parsers.push(function (inputValue) {
            return inputValue === secretEmptyKey ? '' : inputValue; // set the secretEmptyKey back to empty string
          });
        }
      }
    })
    
  5. a custom filter comparator function that always return true (show all results) when one argument is the secretEmptyKey:

    $scope.stateComparator = function (state, viewValue) {
      return viewValue === secretEmptyKey || (''+state).toLowerCase().indexOf((''+viewValue).toLowerCase()) > -1;
    };
    
  6. remove the limitTo filter to show all results

  7. set max-height and overflow css properties to show scrollbar if content is too long

Done!

runTarm
  • 11,537
  • 1
  • 37
  • 37
  • You sir have made me a very happy man! I've been trying to get this working for weeks, and tried loads of different things. This is a great answer, thank you so much :) – Holland Risley Jul 15 '14 at 21:50
  • 1
    you probably don't need jQuery, you could probably use `.triggerHandler('input')` which is part of angular.element/jqLite but I haven't tested it. – gonzofish Oct 27 '14 at 17:48
  • 3
    Just a quick note for some developers like me, that try to get this working: This hack works well with Angular 1.2.x, with 1.3.x this seems not to work any more. – ConcurrentHashMap Nov 18 '14 at 10:43
  • 3
    @runTarm: The solution does not seem to work anymore with Angular 1.3x. Any idea? Thanks – Ritesh Kumar Gupta Apr 25 '15 at 14:51
  • I have tried your solution and it works fine. Just 1 small request. When input element is focused, the suggestions show up. Upto this point it works fine. Now, still the input is empty and if I press TAB, text of the first item automatically comes to the input. I don't want this behavior. I want to have it optional if Empty – Vishal Aug 05 '16 at 10:17
  • @Vishal can't you just add typeahead-focus-first="false" to the input attributes? – Jonas Eriksson Sep 09 '16 at 13:53
12

I got a working solution by changing some code in ui-bootstrap-tpls-0.10.0.js. So there are no differences in the typeahead html markup.

You can have a look here at http://plnkr.co/edit/LXHDpL?p=preview.

To use this fix, use the ui-bootstrap-tpls-0.10.0.js from the Plunk. To see my changes, open ui-bootstrap-tpls-0.10.0.js from the Plunk and search for 'ahneo'.

 1. //minimal no of characters that needs to be entered before typeahead
    kicks-in
    // ahneo :: before
    //var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;
    // ahneo :: after (changed minimal no of characters to 0 by default)
    var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 0;
 2. // ahneo :: new (set input value to empty string if it contains " " string value)
    if (inputValue === ' ') {
        inputValue = '';
        modelCtrl.$setViewValue('');
    }  
 3. // ahneo :: before
    //if (inputValue && inputValue.length >= minSearch) {
    // ahneo :: after (add new condition to get matches for min search = 0)
    if (minSearch === 0 || inputValue && inputValue.length >= minSearch) {
 4. // ahneo :: new (bind element to focus event to trigger modelCtrl.$parsers.unshift method)
    element.bind('focus', function (evt) {
        if (modelCtrl.$viewValue === '') {
            modelCtrl.$setViewValue(' ');
        }
    });

Hope this helps

Henry Neo
  • 2,357
  • 1
  • 24
  • 27
  • This works in .11 as well! Also, I had to combine your solution with the ng-focus from runTarm's solution to trigger the event. Plnkr here: http://plnkr.co/edit/kpl4EE?p=preview – FinDev Sep 05 '14 at 12:17
  • Your answer should be the accepted one. Far better... Thank you very much, Henry! – Aleksandrus Feb 11 '15 at 21:33
  • I have tried all the answers here but found this to work best for me. My problem was that i cannot modify ui-bootstrap-tpls-0.10.0.js. I created a custom directive with your code and it works very well. – vcaz konzam Jan 13 '16 at 18:33
6

Now, as I don't have enough reputation to comment, I must write a new answer to warn people about runTarm's answer above. This is a viable solution, but it runs the risk of running into the following error:

Error: [$rootScope:inprog] $apply already in progress

This seems to be due to ng-focus being a synchronized event (see discussion here). Instead, one can use the ng-click-attribute, and this error doesn't occur.

Also, I've verified that

$element.triggerHandler('input');

works just as good as the jQuery-trigger in runTarm's answer.

Lyckberg
  • 121
  • 1
  • 6
5

Seems like built-in support for this feature is coming in an upcoming release in the form of typeahead-min-length attribute supporting value of 0.

It is implemented in this commit in the master branch https://github.com/angular-ui/bootstrap/commit/d859f42cc022a5d8779f1c7b358486bbdd04ed57, but there is no release with this yet and it's not in the 0.14.x branch.

Hopefully a new release will come quickly so that there is no need for these workarounds anymore.

Cvuorinen
  • 1,319
  • 1
  • 11
  • 14
4

I wanted something like the OP's description and the only solution I found was to come up with a template that combines the dropdown and typeahead directives - maybe the OP or someone else will find it useful:

angular.module('app', ['ui.bootstrap'])
.controller('AppCtrl', function($scope) {
  $scope.model;
  $scope.options = [{label:'Option 1'}, {label:'Option 2'}, {label:'Option 3'}];
  
  $scope.onSelect = function($item, $model, $label) {
    $scope.model = angular.copy($item);
  }
});
<link href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css" rel="stylesheet"/>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.12.0/ui-bootstrap-tpls.min.js"></script>

<div ng-app='app' style='padding:10px'>
  <div ng-controller='AppCtrl'>
    <div class='dropdown' dropdown style='width:200px'>
      <!-- This needs UI Bootstrap 0.12 to work -->
      <div class='input-group'>
        <input type='text' class='form-control' ng-model='model.label' typeahead="op.label for op in options | filter:$viewValue | limitTo:8" typeahead-editable='false' />
        <span class='input-group-btn'>
          <button class='btn btn-default dropdown-toggle' dropdown-toggle>
            <span class='caret'></span>
          </button>
        </span>
      </div>
      <ul class="dropdown-menu" role='menu' style='max-height:200px;overflow-y:scroll'>
        <li ng-repeat='op in options'>
          <a href ng-click='onSelect(op)'>{{op.label}}</a>
        </li>
      </ul>
    </div>
  </div>
</div>

Of course, you could simplify it to make the options just an array of strings- I made them objects because that was more like what I needed.

spongessuck
  • 1,023
  • 8
  • 23
  • I created a module for this template and added a few tweaks: https://github.com/spongessuck/gm.typeaheadDropdown – spongessuck Jan 30 '15 at 18:58
3

typeahead-min-length="0" does the trick (I am using v0.4.0)

cnuis2cool
  • 131
  • 1
  • 5
2

I got solved this issue through a directive. When you uses this directive, shows the list without some filter then, you type your search to find an element.

angular.module('myapp')
.directive('typeaheadLikeSelect', 
['$parse',function($parse) {
    return {
        require: 'ngModel',
        link: function (scope, element, attr, ngModel){

            var aux_modelValue, aux_viewValue,
                modelGetter = $parse(attr.ngModel),
                modelSetter = modelGetter.assign;

            var noViewValue = function(){
              return
              ngModel.$$lastCommittedViewValue === undefined ||
              !ngModel.$$lastCommittedViewValue.trim();
            };

            var forceEvent = function(){
              ngModel.$setViewValue();
              ngModel.$viewValue = ' ';
              ngModel.$setViewValue(' ');
              ngModel.$render();
              scope.$apply();
              element.val(element.val().trim());
            };

            element.on('mousedown', function(e){
              e.stopPropagation();
              forceEvent();
            });

            element.on('blur', function(e){
              e.stopPropagation();
              if(aux_modelValue){
                modelSetter(scope, aux_modelValue);
                scope.$apply();
              }
            });

            scope.$watch(function () {
              return ngModel.$modelValue;
            }, function(newValue, oldValue){
              if(newValue || (!newValue && !oldValue))
                aux_modelValue = newValue;
            });

        }
    };
}]);

I leave a view code, to testing the above code.

<script type="text/ng-template" id="customTemplate.html">
      <a>
          <span ng-bind-html="match.label.description | uibTypeaheadHighlight:query"></span>
      </a>
</script>
    <div class="form-group has-feedback" ng-class="{'has-success':items.mymodel}">
            <input
                placeholder="typeahead"
                ng-model="items.mymodel"
                uib-typeahead="responses as responses for responses in getResponses($viewValue)"
                typeahead-template-url="customTemplate.html"
                typeahead-input-formatter="$model.description"
                typeahead-loading="loadingResponses"
                typeahead-no-results="noResponses"
                typeahead-editable="false"
                typeahead-on-select="changeItem($item)"
                class="form-control"
              required="required"
              typeahead-like-select>
              <div ng-show="noResponses">
                <i class="glyphicon glyphicon-remove"></i> No Results Found
              </div>

              <span ng-show="!items.mymodel" class="glyphicon glyphicon-search form-control-feedback" aria-hidden="true"></span>
              <span ng-show="items.mymodel" class="glyphicon glyphicon-ok form-control-feedback" aria-hidden="true"></span>
          </div>
0

I wanted the typeahead to open whenever my input element had focus. @yohairosen's solution didn't work for me on the latest version of Angular Bootstrap (Version: 1.0.3). Here's the solution that worked for me. It involved manually invoking the parser attached by ui-bootstrap-typeahead which populates the suggestions:

angular.module('app')
.directive('typeaheadFocus', function () {
  return {
      require: 'ngModel',
      link: function (scope, element, attr, ngModel) {
        element.bind('click', function () {
          ngModel.$parsers[0](ngModel.$viewValue);
        });
      }
    };
  };
});

This might be buggy because it assumes the parser added by ui-bootstrap-typeahead is the only one.

jake
  • 150
  • 1
  • 6
0

What we'd want is to trigger('input') on the input element when it's focused.

The correct way to do it in Angular is to do it in a directive.

angular.module('app')    
.directive('showList', function() {
   return {
       restrict: 'A',
       link: function(scope, iEle) {
           iEle.focus(function() {
               iEle.trigger('input');
           });
       }
   };
});

Use this directive on the typeahead input element -

<input show-list uib-typeahead="state for state in states | filter:$viewValue" typeahead-min-length="0" class="form-control">
0

you can achieve by this code

 $scope.change = function() {
        var e = document.getElementById("test");
        var $e = angular.element(e);
        $e.triggerHandler('focus');
        $e.triggerHandler('input');
    }

change the test to your type-head ID

User_3535
  • 826
  • 1
  • 13
  • 30