From 8052dc3727f60b61e5e70a1b35a7b2bd9245b50d Mon Sep 17 00:00:00 2001 From: Christopher Rued Date: Tue, 12 Apr 2016 15:20:59 -0400 Subject: [PATCH] feat(typeahead): Add support for a `typeahead-should-select` function By default behaves as before. If specified, allows a custom function to be defined to determine whether the keydown event should trigger selection. Closes #5671 --- src/typeahead/docs/readme.md | 5 ++++ src/typeahead/test/typeahead.spec.js | 42 ++++++++++++++++++++++++++++ src/typeahead/typeahead.js | 42 +++++++++++++++++----------- 3 files changed, 72 insertions(+), 17 deletions(-) diff --git a/src/typeahead/docs/readme.md b/src/typeahead/docs/readme.md index caae7b624c..5ea5c36fd3 100644 --- a/src/typeahead/docs/readme.md +++ b/src/typeahead/docs/readme.md @@ -77,6 +77,11 @@ This directive works with promises, meaning you can retrieve matches using the ` _(Default: `angular.noop`)_ - Binding to a variable that indicates if no matching results were found. +* `typeahead-should-select($event)` + $ + _(Default: `null`)_ - + A callback executed when a `keyup` event that might trigger a selection occurs. Selection will only occur if this function returns true. + * `typeahead-on-select($item, $model, $label, $event)` $ _(Default: `null`)_ - diff --git a/src/typeahead/test/typeahead.spec.js b/src/typeahead/test/typeahead.spec.js index 825b008738..bd00e961a6 100644 --- a/src/typeahead/test/typeahead.spec.js +++ b/src/typeahead/test/typeahead.spec.js @@ -457,6 +457,48 @@ describe('typeahead tests', function() { }); }); + describe('shouldSelect', function() { + it('should select a match when function returns true', function() { + $scope.shouldSelectFn = function() { + return true; + }; + var element = prepareInputEl('
'); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + expect($scope.result).toEqual('bar'); + expect(inputEl.val()).toEqual('bar'); + expect(element).toBeClosed(); + }); + it('should not select a match when function returns false', function() { + $scope.shouldSelectFn = function() { + return false; + }; + var element = prepareInputEl('
'); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + // no change + expect($scope.result).toEqual('b'); + expect(inputEl.val()).toEqual('b'); + }); + it('should pass key event into select trigger function', function() { + $scope.shouldSelectFn = jasmine.createSpy('shouldSelectFn');//.and.returnValue(true); + var element = prepareInputEl('
'); + var inputEl = findInput(element); + + changeInputValueTo(element, 'b'); + triggerKeyDown(element, 13); + + expect($scope.shouldSelectFn.calls.count()).toEqual(1); + expect($scope.shouldSelectFn.calls.argsFor(0)[0].which).toEqual(13); + }); + }); + describe('selecting a match', function() { it('should select a match on enter', function() { var element = prepareInputEl('
'); diff --git a/src/typeahead/typeahead.js b/src/typeahead/typeahead.js index 14169c6371..f9a81b4a37 100644 --- a/src/typeahead/typeahead.js +++ b/src/typeahead/typeahead.js @@ -42,7 +42,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap originalScope.$watch(attrs.typeaheadMinLength, function (newVal) { minLength = !newVal && newVal !== 0 ? 1 : newVal; }); - + //minimal wait time after last character typed before typeahead kicks-in var waitTime = originalScope.$eval(attrs.typeaheadWaitMs) || 0; @@ -55,6 +55,12 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap //binding to a variable that indicates if matches are being retrieved asynchronously var isLoadingSetter = $parse(attrs.typeaheadLoading).assign || angular.noop; + //a function to determine if an event should cause selection + var isSelectEvent = attrs.typeaheadShouldSelect ? $parse(attrs.typeaheadShouldSelect) : function(scope, vals) { + var evt = vals.$event; + return evt.which === 13 || evt.which === 9; + }; + //a callback executed when a match is selected var onSelectCallback = $parse(attrs.typeaheadOnSelect); @@ -370,13 +376,15 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap return; } + var shouldSelect = isSelectEvent(originalScope, {$event: evt}); + /** * if there's nothing selected (i.e. focusFirst) and enter or tab is hit * or * shift + tab is pressed to bring focus to the previous element * then clear the results */ - if (scope.activeIdx === -1 && (evt.which === 9 || evt.which === 13) || evt.which === 9 && !!evt.shiftKey) { + if (scope.activeIdx === -1 && shouldSelect || evt.which === 9 && !!evt.shiftKey) { resetMatches(); scope.$digest(); return; @@ -385,36 +393,36 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.debounce', 'ui.bootstrap evt.preventDefault(); var target; switch (evt.which) { - case 9: - case 13: - scope.$apply(function () { - if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { - $$debounce(function() { - scope.select(scope.activeIdx, evt); - }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); - } else { - scope.select(scope.activeIdx, evt); - } - }); - break; - case 27: + case 27: // escape evt.stopPropagation(); resetMatches(); originalScope.$digest(); break; - case 38: + case 38: // up arrow scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1; scope.$digest(); target = popUpEl.find('li')[scope.activeIdx]; target.parentNode.scrollTop = target.offsetTop; break; - case 40: + case 40: // down arrow scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length; scope.$digest(); target = popUpEl.find('li')[scope.activeIdx]; target.parentNode.scrollTop = target.offsetTop; break; + default: + if (shouldSelect) { + scope.$apply(function() { + if (angular.isNumber(scope.debounceUpdate) || angular.isObject(scope.debounceUpdate)) { + $$debounce(function() { + scope.select(scope.activeIdx, evt); + }, angular.isNumber(scope.debounceUpdate) ? scope.debounceUpdate : scope.debounceUpdate['default']); + } else { + scope.select(scope.activeIdx, evt); + } + }); + } } });