Skip to content
This repository has been archived by the owner on May 29, 2019. It is now read-only.

feat(typeahead): Adds 'select on exact' setting. #3365

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions src/typeahead/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ The typeahead directives provide several attributes:
* `typeahead-editable` <i class="glyphicon glyphicon-eye-open"></i>
_(Defaults: true)_ :
Should it restrict model values to the ones selected from the popup only ?

* `typeahead-focus-first`
_(Defaults: true)_ :
Should the first match automatically be focused as you type?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this was moved?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I was looking for where to add the documentation for the new attribute, I noticed that they were almost in alphabetical order (except for focus-first) and assumed that they were supposed to be so I moved 'focus-first' to higher up in the list. Looking again, I see I misplaced 'select-on-exact' anyway... so I've fixed that now. I'm happy to order them differently if I'm incorrect.


* `typeahead-input-formatter` <i class="glyphicon glyphicon-eye-open"></i>
_(Defaults: undefined)_ :
Expand All @@ -39,11 +43,15 @@ The typeahead directives provide several attributes:
* `typeahead-min-length` <i class="glyphicon glyphicon-eye-open"></i>
_(Defaults: 1)_ :
Minimal no of characters that needs to be entered before typeahead kicks-in

* `typeahead-on-select($item, $model, $label)`
_(Defaults: null)_ :
A callback executed when a match is selected

* `typeahead-select-on-exact`
_(Defaults: false)_ :
Should it automatically select an item when there is one option that exactly matches the user input?

* `typeahead-template-url` <i class="glyphicon glyphicon-eye-open"></i>
:
Set custom item template
Expand All @@ -52,10 +60,6 @@ The typeahead directives provide several attributes:
_(Defaults: 0)_ :
Minimal wait time after last character typed before typeahead kicks-in

* `typeahead-focus-first`
_(Defaults: true)_ :
Should the first match automatically be focused as you type?

* `typeahead-select-on-blur`
_(Defaults: false)_ :
On blur, select the currently highlighted match
57 changes: 57 additions & 0 deletions src/typeahead/test/typeahead.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,63 @@ describe('typeahead tests', function () {
expect(inputEl.val()).toEqual('AL');
});
});

describe('select on exact match', function(){

it('should select on an exact match when set', function () {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of these should be grouped under a describe block with the select on an exact match descriptor string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I see I missed that. Done.


$scope.onSelect = jasmine.createSpy('onSelect');
var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
var inputEl = findInput(element);

changeInputValueTo(element, 'bar');

expect($scope.result).toEqual('bar');
expect(inputEl.val()).toEqual('bar');
expect(element).toBeClosed();
expect($scope.onSelect).toHaveBeenCalled();
});

it('should not select on an exact match by default', function () {

$scope.onSelect = jasmine.createSpy('onSelect');
var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" typeahead="item for item in source | filter:$viewValue"></div>');
var inputEl = findInput(element);

changeInputValueTo(element, 'bar');

expect($scope.result).toBeUndefined();
expect(inputEl.val()).toEqual('bar');
expect($scope.onSelect.calls.any()).toBe(false);
});

it('should not be case sensitive when select on an exact match', function () {

$scope.onSelect = jasmine.createSpy('onSelect');
var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
var inputEl = findInput(element);

changeInputValueTo(element, 'BaR');

expect($scope.result).toEqual('bar');
expect(inputEl.val()).toEqual('bar');
expect(element).toBeClosed();
expect($scope.onSelect).toHaveBeenCalled();
});

it('should not auto select when not a match with one potential result left', function () {

$scope.onSelect = jasmine.createSpy('onSelect');
var element = prepareInputEl('<div><input ng-model="result" typeahead-editable="false" typeahead-on-select="onSelect()" typeahead="item for item in source | filter:$viewValue" typeahead-select-on-exact="true"></div>');
var inputEl = findInput(element);

changeInputValueTo(element, 'fo');

expect($scope.result).toBeUndefined();
expect(inputEl.val()).toEqual('fo');
expect($scope.onSelect.calls.any()).toBe(false);
});
});

describe('pop-up interaction', function () {
var element;
Expand Down
17 changes: 17 additions & 0 deletions src/typeahead/typeahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
var appendToBody = attrs.typeaheadAppendToBody ? originalScope.$eval(attrs.typeaheadAppendToBody) : false;

var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;

//If input matches an item of the list exactly, select it automatically
var selectOnExact = attrs.typeaheadSelectOnExact ? originalScope.$eval(attrs.typeaheadSelectOnExact) : false;

//INTERNAL VARIABLES

Expand Down Expand Up @@ -133,6 +136,15 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
element.attr('aria-activedescendant', getMatchId(index));
}
});

var inputIsExactMatch = function(inputValue, index) {

if (scope.matches.length > index && inputValue){
return inputValue.toUpperCase() === scope.matches[index].label.toUpperCase();
}

return false;
};

var getMatchesAsync = function(inputValue) {

Expand Down Expand Up @@ -166,6 +178,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
recalculatePosition();

element.attr('aria-expanded', true);

//Select the single remaining option if user input matches
if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)){
scope.select(0);
}
} else {
resetMatches();
}
Expand Down