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

Commit

Permalink
feat(typeahead): Add support for a typeahead-should-select function
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Christopher Rued authored and Christopher Rued committed May 17, 2016
1 parent e0fcc00 commit 8052dc3
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 17 deletions.
5 changes: 5 additions & 0 deletions src/typeahead/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)`
<small class="badge">$</small>
_(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)`
<small class="badge">$</small>
_(Default: `null`)_ -
Expand Down
42 changes: 42 additions & 0 deletions src/typeahead/test/typeahead.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<div><input ng-model="result" typeahead-should-select="shouldSelectFn($event)" uib-typeahead="item for item in source | filter:$viewValue"></div>');
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('<div><input ng-model="result" typeahead-should-select="shouldSelectFn($event)" uib-typeahead="item for item in source | filter:$viewValue"></div>');
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('<div><input ng-model="result" typeahead-should-select="shouldSelectFn($event)" uib-typeahead="item for item in source | filter:$viewValue"></div>');
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('<div><input ng-model="result" uib-typeahead="item for item in source | filter:$viewValue"></div>');
Expand Down
42 changes: 25 additions & 17 deletions src/typeahead/typeahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);

Expand Down Expand Up @@ -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;
Expand All @@ -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);
}
});
}
}
});

Expand Down

0 comments on commit 8052dc3

Please sign in to comment.