Skip to content

Commit

Permalink
feat(typeahead): add focus-first option
Browse files Browse the repository at this point in the history
Add typeahead-focus-first option to prevent first match from being
focused.

Currently, the first result is automatically focused as you type. Now, set
`typeahead-focus-first="false"` and the first result is *not*
automatically focused as you type.

Closes angular-ui#908
Closes angular-ui#2916
  • Loading branch information
wkonkel authored and Oron Nadiv committed Nov 18, 2014
1 parent 3b8fc16 commit b7ad1b8
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 4 deletions.
9 changes: 9 additions & 0 deletions dist/ui-bootstrap-0.13.0-SNAPSHOT.min.js

Large diffs are not rendered by default.

10 changes: 10 additions & 0 deletions dist/ui-bootstrap-tpls-0.13.0-SNAPSHOT.min.js

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions src/typeahead/docs/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ The typeahead directives provide several attributes:
* `typeahead-wait-ms` <i class="glyphicon glyphicon-eye-open"></i>
_(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?
97 changes: 95 additions & 2 deletions src/typeahead/test/typeahead.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,12 @@ describe('typeahead tests', function () {
this.message = function () {
return 'Expected "' + this.actual + '" to be opened.';
};
return typeaheadEl.length === 1 && typeaheadEl.hasClass('ng-hide') === false && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active');

return (typeaheadEl.length === 1 &&
typeaheadEl.hasClass('ng-hide') === false &&
liEls.length === noOfMatches &&
(activeIdx === -1 ? !$(liEls).hasClass('active') : $(liEls[activeIdx]).hasClass('active'))
);
}
});
});
Expand Down Expand Up @@ -409,7 +414,7 @@ describe('typeahead tests', function () {
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 1);

// Up arrow key goes back to last element
// Up arrow key goes back to first element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 0);
});
Expand Down Expand Up @@ -670,4 +675,92 @@ describe('typeahead tests', function () {
});
});

describe('focus first', function () {
it('should focus the first element by default', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue"></div>');
changeInputValueTo(element, 'b');
expect(element).toBeOpenWithActive(2, 0);

// Down arrow key
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 1);

// Down arrow key goes back to first element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 0);

// Up arrow key goes back to last element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 1);

// Up arrow key goes back to first element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 0);
});

it('should not focus the first element until keys are pressed', function () {
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-focus-first="false"></div>');
changeInputValueTo(element, 'b');
expect(element).toBeOpenWithActive(2, -1);

// Down arrow key goes to first element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 0);

// Down arrow key goes to second element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 1);

// Down arrow key goes back to first element
triggerKeyDown(element, 40);
expect(element).toBeOpenWithActive(2, 0);

// Up arrow key goes back to last element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 1);

// Up arrow key goes back to first element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 0);

// New input goes back to no focus
changeInputValueTo(element, 'a');
changeInputValueTo(element, 'b');
expect(element).toBeOpenWithActive(2, -1);

// Up arrow key goes to last element
triggerKeyDown(element, 38);
expect(element).toBeOpenWithActive(2, 1);
});
});

it('should not capture enter or tab until an item is focused', function () {
$scope.select_count = 0;
$scope.onSelect = function ($item, $model, $label) {
$scope.select_count = $scope.select_count + 1;
};
var element = prepareInputEl('<div><input ng-model="result" ng-keydown="keyDownEvent = $event" typeahead="item for item in source | filter:$viewValue" typeahead-on-select="onSelect($item, $model, $label)" typeahead-focus-first="false"></div>');
changeInputValueTo(element, 'b');

// enter key should not be captured when nothing is focused
triggerKeyDown(element, 13);
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
expect($scope.select_count).toEqual(0);

// tab key should not be captured when nothing is focused
triggerKeyDown(element, 9);
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
expect($scope.select_count).toEqual(0);

// down key should be captured and focus first element
triggerKeyDown(element, 40);
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
expect(element).toBeOpenWithActive(2, 0);

// enter key should be captured now that something is focused
triggerKeyDown(element, 13);
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
expect($scope.select_count).toEqual(1);
});

});
11 changes: 9 additions & 2 deletions src/typeahead/typeahead.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap

var leftOffset = originalScope.$eval(attrs.typeaheadPopupLeftOffset) || 0;

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

//INTERNAL VARIABLES

//model setter executed upon match selection
Expand Down Expand Up @@ -136,7 +138,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
if (onCurrentRequest && hasFocus) {
if (matches.length > 0) {

scope.activeIdx = 0;
scope.activeIdx = focusFirst ? 0 : -1;
scope.matches.length = 0;

//transform labels
Expand Down Expand Up @@ -290,6 +292,11 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap
return;
}

// if there's nothing selected (i.e. focusFirst) and enter is hit, don't do anything
if (scope.activeIdx == -1 && (evt.which === 13 || evt.which === 9)) {
return;
}

evt.preventDefault();

var i;
Expand All @@ -304,7 +311,7 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position', 'ui.bootstrap

} else if (evt.which === 38) {
for (i = 0; i < scope.matches.length; i++) {
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
if (!scope.matches[scope.activeIdx].model.notSelectable) {
break;
}
Expand Down

0 comments on commit b7ad1b8

Please sign in to comment.