Skip to content

Commit 8f3f260

Browse files
committed
Merge pull request #214 from robbse/master
Adding typeahead-focus-first=false option
2 parents 84f9a14 + cbd051c commit 8f3f260

File tree

3 files changed

+110
-6
lines changed

3 files changed

+110
-6
lines changed

src/typeahead/docs/readme.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,3 +48,7 @@ The typeahead directives provide several attributes:
4848
* `typeahead-wait-ms` <i class="glyphicon glyphicon-eye-open"></i>
4949
_(Defaults: 0)_ :
5050
Minimal wait time after last character typed before typeahead kicks-in
51+
52+
* `typeahead-focus-first` <i class="glyphicon glyphicon-eye-open"></i>
53+
_(Defaults: true)_ :
54+
Should the first match automatically be focused?

src/typeahead/test/typeahead.spec.js

Lines changed: 97 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,18 @@ describe('typeahead tests', function () {
7474
return typeaheadEl.css('display') === 'none' && findMatches(this.actual).length === 0;
7575

7676
}, toBeOpenWithActive: function (noOfMatches, activeIdx) {
77-
77+
7878
var typeaheadEl = findDropDown(this.actual);
7979
var liEls = findMatches(this.actual);
8080

8181
this.message = function () {
8282
return "Expected '" + this.actual + "' to be opened.";
8383
};
84-
return typeaheadEl.css('display') === 'block' && liEls.length === noOfMatches && $(liEls[activeIdx]).hasClass('active');
84+
return (typeaheadEl.length === 1 &&
85+
typeaheadEl.hasClass('ng-hide') === false &&
86+
liEls.length === noOfMatches &&
87+
(activeIdx === -1 ? !$(liEls).hasClass('active') : $(liEls[activeIdx]).hasClass('active'))
88+
);
8589
}
8690
});
8791
});
@@ -258,7 +262,7 @@ describe('typeahead tests', function () {
258262
return $scope.source;
259263
};
260264
var element = prepareInputEl("<div><input ng-model='result' typeahead='item for item in loadMatches($viewValue) | filter:$viewValue' typeahead-wait-ms='200'></div>");
261-
265+
262266
changeInputValueTo(element, 'first');
263267
$timeout.flush();
264268

@@ -389,7 +393,7 @@ describe('typeahead tests', function () {
389393
triggerKeyDown(element, 38);
390394
expect(element).toBeOpenWithActive(2, 1);
391395

392-
// Up arrow key goes back to last element
396+
// Up arrow key goes back to first element
393397
triggerKeyDown(element, 38);
394398
expect(element).toBeOpenWithActive(2, 0);
395399
});
@@ -634,4 +638,93 @@ describe('typeahead tests', function () {
634638
});
635639
});
636640

641+
describe('focus first', function () {
642+
it('should focus the first element by default', function () {
643+
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue"></div>');
644+
changeInputValueTo(element, 'b');
645+
expect(element).toBeOpenWithActive(2, 0);
646+
647+
// Down arrow key
648+
triggerKeyDown(element, 40);
649+
expect(element).toBeOpenWithActive(2, 1);
650+
651+
// Down arrow key goes back to first element
652+
triggerKeyDown(element, 40);
653+
expect(element).toBeOpenWithActive(2, 0);
654+
655+
// Up arrow key goes back to last element
656+
triggerKeyDown(element, 38);
657+
expect(element).toBeOpenWithActive(2, 1);
658+
659+
// Up arrow key goes back to first element
660+
triggerKeyDown(element, 38);
661+
expect(element).toBeOpenWithActive(2, 0);
662+
});
663+
664+
it('should not focus the first element until keys are pressed', function () {
665+
var element = prepareInputEl('<div><input ng-model="result" typeahead="item for item in source | filter:$viewValue" typeahead-focus-first="false"></div>');
666+
changeInputValueTo(element, 'b');
667+
expect(element).toBeOpenWithActive(2, -1);
668+
669+
// Down arrow key goes to first element
670+
triggerKeyDown(element, 40);
671+
expect(element).toBeOpenWithActive(2, 0);
672+
673+
// Down arrow key goes to second element
674+
triggerKeyDown(element, 40);
675+
expect(element).toBeOpenWithActive(2, 1);
676+
677+
// Down arrow key goes back to first element
678+
triggerKeyDown(element, 40);
679+
expect(element).toBeOpenWithActive(2, 0);
680+
681+
// Up arrow key goes back to last element
682+
triggerKeyDown(element, 38);
683+
expect(element).toBeOpenWithActive(2, 1);
684+
685+
// Up arrow key goes back to first element
686+
triggerKeyDown(element, 38);
687+
expect(element).toBeOpenWithActive(2, 0);
688+
689+
// New input goes back to no focus
690+
changeInputValueTo(element, 'a');
691+
changeInputValueTo(element, 'b');
692+
expect(element).toBeOpenWithActive(2, -1);
693+
694+
// Up arrow key goes to last element
695+
triggerKeyDown(element, 38);
696+
expect(element).toBeOpenWithActive(2, 1);
697+
});
698+
});
699+
700+
it('should not capture enter or tab until an item is focused', function () {
701+
$scope.select_count = 0;
702+
$scope.onSelect = function ($item, $model, $label) {
703+
$scope.select_count = $scope.select_count + 1;
704+
};
705+
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>');
706+
changeInputValueTo(element, 'b');
707+
708+
// enter key should not be captured when nothing is focused
709+
triggerKeyDown(element, 13);
710+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
711+
expect($scope.select_count).toEqual(0);
712+
713+
// tab key should not be captured when nothing is focused
714+
triggerKeyDown(element, 9);
715+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeFalsy();
716+
expect($scope.select_count).toEqual(0);
717+
718+
// down key should be captured and focus first element
719+
triggerKeyDown(element, 40);
720+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
721+
expect(element).toBeOpenWithActive(2, 0);
722+
723+
// enter key should be captured now that something is focused
724+
triggerKeyDown(element, 13);
725+
expect($scope.keyDownEvent.isDefaultPrevented()).toBeTruthy();
726+
expect($scope.select_count).toEqual(1);
727+
});
728+
729+
637730
});

src/typeahead/typeahead.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundat
5959

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

62+
var focusFirst = originalScope.$eval(attrs.typeaheadFocusFirst) !== false;
63+
6264
//INTERNAL VARIABLES
6365

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

109-
scope.activeIdx = 0;
111+
scope.activeIdx = focusFirst ? 0 : -1;
110112
scope.matches.length = 0;
111113

112114
//transform labels
@@ -231,14 +233,19 @@ angular.module('mm.foundation.typeahead', ['mm.foundation.position', 'mm.foundat
231233
return;
232234
}
233235

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

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

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

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

0 commit comments

Comments
 (0)