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

Commit ef82ad1

Browse files
chenyuzhcywesleycho
authored andcommitted
feat(typeahead): add show-hint option
- Add support for hinting on the first match in the matches list Closes #2570 Closes #4784
1 parent 9d93af1 commit ef82ad1

File tree

4 files changed

+134
-1
lines changed

4 files changed

+134
-1
lines changed

Diff for: src/typeahead/docs/demo.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ <h4>Asynchronous results</h4>
6666

6767
<h4>Custom templates for results</h4>
6868
<pre>Model: {{customSelected | json}}</pre>
69-
<input type="text" ng-model="customSelected" placeholder="Custom template" uib-typeahead="state as state.name for state in statesWithFlags | filter:{name:$viewValue}" typeahead-template-url="customTemplate.html" class="form-control">
69+
<input type="text" ng-model="customSelected" placeholder="Custom template" uib-typeahead="state as state.name for state in statesWithFlags | filter:{name:$viewValue}" typeahead-template-url="customTemplate.html" class="form-control" typeahead-show-hint="true">
7070

7171
<h4>Custom popup templates for typeahead's dropdown</h4>
7272
<pre>Model: {{customPopupSelected | json}}</pre>

Diff for: src/typeahead/docs/readme.md

+4
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,7 @@ The typeahead directives provide several attributes:
8282
* `typeahead-is-open` <i class="glyphicon glyphicon-eye-open"></i>
8383
_(Defaults: angular.noop)_ :
8484
Binding to a variable that indicates if dropdown is open
85+
86+
* `typeahead-show-hint`
87+
_(Defaults: false)_ :
88+
Should input show hint that matches the first option?

Diff for: src/typeahead/test/typeahead.spec.js

+79
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,85 @@ describe('typeahead tests', function() {
967967
expect($scope.result).toEqual($scope.states[0]);
968968
});
969969
});
970+
971+
describe('input hint', function() {
972+
var element;
973+
974+
beforeEach(function() {
975+
element = prepareInputEl('<div><input ng-model="result" uib-typeahead="state.name for state in states| filter:$viewValue" typeahead-show-hint="true"></div>');
976+
});
977+
978+
it('should show hint when input matches first match', function() {
979+
var hintEl = findInput(element);
980+
981+
expect(hintEl.val()).toEqual('');
982+
changeInputValueTo(element, 'Alas');
983+
expect(hintEl.val()).toEqual('Alaska');
984+
});
985+
986+
it('should not show hint when input does not match first match', function() {
987+
var hintEl = findInput(element);
988+
989+
expect(hintEl.val()).toEqual('');
990+
changeInputValueTo(element, 'las');
991+
expect(hintEl.val()).toEqual('');
992+
});
993+
994+
it('should reset hint when a match is clicked', function() {
995+
var hintEl = findInput(element);
996+
997+
expect(hintEl.val()).toEqual('');
998+
changeInputValueTo(element, 'Alas');
999+
expect(hintEl.val()).toEqual('Alaska');
1000+
1001+
var match = findMatches(element).find('a').eq(0);
1002+
match.click();
1003+
$scope.$digest();
1004+
expect(hintEl.val()).toEqual('');
1005+
});
1006+
1007+
it('should reset hint when click outside', function() {
1008+
var hintEl = findInput(element);
1009+
1010+
expect(hintEl.val()).toEqual('');
1011+
changeInputValueTo(element, 'Alas');
1012+
expect(hintEl.val()).toEqual('Alaska');
1013+
1014+
$document.find('body').click();
1015+
$scope.$digest();
1016+
expect(hintEl.val()).toEqual('');
1017+
});
1018+
1019+
it('should reset hint on enter', function() {
1020+
var hintEl = findInput(element);
1021+
1022+
expect(hintEl.val()).toEqual('');
1023+
changeInputValueTo(element, 'Alas');
1024+
expect(hintEl.val()).toEqual('Alaska');
1025+
triggerKeyDown(element, 13);
1026+
expect(hintEl.val()).toEqual('');
1027+
});
1028+
1029+
it('should reset hint on tab', function() {
1030+
var hintEl = findInput(element);
1031+
1032+
expect(hintEl.val()).toEqual('');
1033+
changeInputValueTo(element, 'Alas');
1034+
expect(hintEl.val()).toEqual('Alaska');
1035+
triggerKeyDown(element, 9);
1036+
expect(hintEl.val()).toEqual('');
1037+
});
1038+
1039+
it('should reset hint on escape key', function() {
1040+
var hintEl = findInput(element);
1041+
1042+
expect(hintEl.val()).toEqual('');
1043+
changeInputValueTo(element, 'Alas');
1044+
expect(hintEl.val()).toEqual('Alaska');
1045+
triggerKeyDown(element, 27);
1046+
expect(hintEl.val()).toEqual('');
1047+
});
1048+
});
9701049

9711050
describe('append to element id', function() {
9721051
it('append typeahead results to element', function() {

Diff for: src/typeahead/typeahead.js

+50
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
7171
//binding to a variable that indicates if dropdown is open
7272
var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;
7373

74+
var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;
75+
7476
//INTERNAL VARIABLES
7577

7678
//model setter executed upon match selection
@@ -111,6 +113,33 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
111113
'aria-owns': popupId
112114
});
113115

116+
//add read-only input to show hint
117+
if (showHint) {
118+
var inputsContainer = angular.element('<div></div>');
119+
inputsContainer.css('position', 'relative');
120+
element.after(inputsContainer);
121+
var hintInputElem = element.clone();
122+
hintInputElem.attr('placeholder', '');
123+
hintInputElem.val('');
124+
hintInputElem.css({
125+
'position': 'absolute',
126+
'top': '0px',
127+
'left': '0px',
128+
'border-color': 'transparent',
129+
'box-shadow': 'none',
130+
'opacity': 1,
131+
'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
132+
'color': '#999'
133+
});
134+
element.css({
135+
'position': 'relative',
136+
'vertical-align': 'top',
137+
'background-color': 'transparent'
138+
});
139+
inputsContainer.append(hintInputElem);
140+
hintInputElem.after(element);
141+
}
142+
114143
//pop-up element used to display matches
115144
var popUpEl = angular.element('<div uib-typeahead-popup></div>');
116145
popUpEl.attr({
@@ -132,10 +161,17 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
132161
popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
133162
}
134163

164+
var resetHint = function() {
165+
if (showHint) {
166+
hintInputElem.val('');
167+
}
168+
};
169+
135170
var resetMatches = function() {
136171
scope.matches = [];
137172
scope.activeIdx = -1;
138173
element.attr('aria-expanded', false);
174+
resetHint();
139175
};
140176

141177
var getMatchId = function(index) {
@@ -196,6 +232,16 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
196232
if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
197233
scope.select(0);
198234
}
235+
236+
if (showHint) {
237+
var firstLabel = scope.matches[0].label;
238+
if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
239+
hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
240+
}
241+
else {
242+
hintInputElem.val('');
243+
}
244+
}
199245
} else {
200246
resetMatches();
201247
isNoResultsSetter(originalScope, true);
@@ -377,6 +423,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
377423
}
378424
// Prevent jQuery cache memory leak
379425
popUpEl.remove();
426+
427+
if (showHint) {
428+
inputsContainer.remove();
429+
}
380430
});
381431

382432
var $popup = $compile(popUpEl)(scope);

0 commit comments

Comments
 (0)