Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding typeahead-focus-first=false option #214

Merged
merged 1 commit into from Jun 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/typeahead/docs/readme.md
Expand Up @@ -48,3 +48,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` <i class="glyphicon glyphicon-eye-open"></i>
_(Defaults: true)_ :
Should the first match automatically be focused?
101 changes: 97 additions & 4 deletions src/typeahead/test/typeahead.spec.js
Expand Up @@ -74,14 +74,18 @@ describe('typeahead tests', function () {
return typeaheadEl.css('display') === 'none' && findMatches(this.actual).length === 0;

}, toBeOpenWithActive: function (noOfMatches, activeIdx) {

var typeaheadEl = findDropDown(this.actual);
var liEls = findMatches(this.actual);

this.message = function () {
return "Expected '" + this.actual + "' to be opened.";
};
return typeaheadEl.css('display') === 'block' && 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 @@ -258,7 +262,7 @@ describe('typeahead tests', function () {
return $scope.source;
};
var element = prepareInputEl("<div><input ng-model='result' typeahead='item for item in loadMatches($viewValue) | filter:$viewValue' typeahead-wait-ms='200'></div>");

changeInputValueTo(element, 'first');
$timeout.flush();

Expand Down Expand Up @@ -389,7 +393,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 @@ -595,4 +599,93 @@ 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
Expand Up @@ -59,6 +59,8 @@ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundat

var appendToBody = attrs.typeaheadAppendToBody ? $parse(attrs.typeaheadAppendToBody) : false;

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

//INTERNAL VARIABLES

//model setter executed upon match selection
Expand Down Expand Up @@ -106,7 +108,7 @@ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundat
if (inputValue === modelCtrl.$viewValue && hasFocus) {
if (matches.length > 0) {

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

//transform labels
Expand Down Expand Up @@ -231,14 +233,19 @@ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundat
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();

if (evt.which === 40) {
scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
scope.$digest();

} else if (evt.which === 38) {
scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
scope.activeIdx = (scope.activeIdx > 0 ? scope.activeIdx : scope.matches.length) - 1;
scope.$digest();

} else if (evt.which === 13 || evt.which === 9) {
Expand Down