diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index 8f1ba6fa56..34f490341a 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -27,6 +27,10 @@ The typeahead directives provide several attributes: * `typeahead-editable` _(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? * `typeahead-input-formatter` _(Defaults: undefined)_ : @@ -39,11 +43,15 @@ The typeahead directives provide several attributes: * `typeahead-min-length` _(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` : Set custom item template @@ -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? - * `select-on`blur` _(Defaults: false)_ : On blur, select the currently highlighted match diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 885546bcc8..a8b38f6492 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -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 () { + + $scope.onSelect = jasmine.createSpy('onSelect'); + var element = prepareInputEl('
'); + 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('
'); + 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('
'); + 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('
'); + 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; diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 0a63f63247..baa2b20940 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -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 @@ -128,6 +131,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) { @@ -161,6 +173,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(); }