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

feat(typeahead): Support for a typeahead-should-select function #5794

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
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