Search code examples
javascriptangularjsangular-ui-bootstraptypeaheadangular-ui-typeahead

Unexpected Behavior in typeahead - is this a bug?


I have an input using typeahead as follows:

<input type="text" id="unit" name="unit" class="form-control form-input" ng-model="item.unit"
            autocomplete="off" 
            typeahead-min-length="0" 
            uib-typeahead="unit as unit.symbol for unit in units | typeaheadFilter:{'symbol':$viewValue} | orderBy:smartOrder" 
            typeahead-template-url="unit-template.html" />

And here is the template:

<script type="text/ng-template" id="unit-template.html">
    <a tabindex="-1">
        <div class="row">
            <span class="col-md-6 col-sm-6" ng-bind-html="match.model.symbol | uibTypeaheadHighlight:query"></span>
            <span class="col-md-5 col-sm-5 col-md-offset-1 col-sm-offset-1" ng-bind-html="match.model.name  | uibTypeaheadHighlight:query"></span>
        </div>
    </a>
</script>

my units collection has two items:

name=kilogram symbol=kg
name=litre symbol=L

At first look I thought that typeahead works fine.

But when I tried the below key combinations, I found a bug.

Case: 1

Working:

When I type kg in typeahead and hit tab twice, the item.unit property has value:

Object {_id: "58cd0cdf28ea727c68be7ac3", name: "Kilogram", symbol: "kg", numberOfDecimalPlaces: 3, isSystemUnit: false…}

Not working:

But when I type kg in typeahead and hit esc and then hit tab, the item.unit property has value:

kg

Case:2

Working:

When I type kg in typeahead and hit tab twice the focus goes away from the control. Now item.unit property has value:

Object {_id: "58cd0cdf28ea727c68be7ac3", name: "Kilogram", symbol: "kg", numberOfDecimalPlaces: 3, isSystemUnit: false…}

And then if I delete the text in typeahead by using delete or backspace key, then if I move focus away from typeahead then item.unit is

undefined.

Not working:

When I type kg in typeahead and hit tab twice the focus goes away from the control. Now item.unit property has value:

Object {_id: "58cd0cdf28ea727c68be7ac3", name: "Kilogram", symbol: "kg", numberOfDecimalPlaces: 3, isSystemUnit: false…}

And then if I delete the text in typeahead by selecting the text and then using delete or backspace key, then I move focus away from typeahead, then item.unit is still having value:

Object {_id: "58cd0cdf28ea727c68be7ac3", name: "Kilogram", symbol: "kg", numberOfDecimalPlaces: 3, isSystemUnit: false…}

I have also raised an issue on their github page.

Plunker:

Here is the link to plunker that reproduces the issue: https://plnkr.co/edit/FIPANC3CcliNOeHHANxF


Solution

  • I don't see a bug with type-ahead

    • In non-working case 1: you are effectively saying cancel type-ahead (which is what esc does) and then your code says display whatever is bound to the input element, which in this case is just whatever you typed in - "kg". That is the expected behaviour (for the given code).

      In other words, you'd get exactly the same result if type-ahead wasn't installed.

    • In non-working case 2, it depends on how you move away after deleting - if you are using tab twice, type-ahead has the default suggestion selected of 'kg' so first tab picks it and then the second moves focus, so I'd expect it to be set to the 'kg' object. If however you are clicking somewhere else after the delete/backspace then the value is the empty string '', which also is what I'd expect given that you're displaying the property bound to the input.

    So the real question is what do you want to happen when these things occur?

    EDIT:

    To return a matching object in non-working case 1 you can do the following - when leaving the field if $scope.unit is not set to an object perform a lookup in the array:

    $scope.unitLostFocus = function(unit) {
      if (typeof unit !== "object") { // typed text so try to match in our array
        var res = jQuery.grep($scope.units, function(n) {
          return ( n.symbol.toLowerCase() === unit.toLowerCase() );
        });
        if (res.length)
          $scope.unit = res[0]; // first match
        else 
          $scope.unit = undefined; // whatever you want to do here when there's no match
      }
      console.log($scope.unit);
    };
    

    Updated Plunker: https://plnkr.co/edit/8YsecsgToP8dtwYHbhD4?p=preview