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

Commit

Permalink
feat(typeahead): add show-hint option
Browse files Browse the repository at this point in the history
- Add support for hinting on the first match in the matches list

Closes #2570
Closes #4784
  • Loading branch information
chenyuzhcy authored and wesleycho committed Oct 30, 2015
1 parent 9d93af1 commit ef82ad1
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 1 deletion.
2 changes: 1 addition & 1 deletion src/typeahead/docs/demo.html
Expand Up @@ -66,7 +66,7 @@ <h4>Asynchronous results</h4>

<h4>Custom templates for results</h4>
<pre>Model: {{customSelected | json}}</pre>
<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">
<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">

<h4>Custom popup templates for typeahead's dropdown</h4>
<pre>Model: {{customPopupSelected | json}}</pre>
Expand Down
4 changes: 4 additions & 0 deletions src/typeahead/docs/readme.md
Expand Up @@ -82,3 +82,7 @@ The typeahead directives provide several attributes:
* `typeahead-is-open` <i class="glyphicon glyphicon-eye-open"></i>
_(Defaults: angular.noop)_ :
Binding to a variable that indicates if dropdown is open

* `typeahead-show-hint`
_(Defaults: false)_ :
Should input show hint that matches the first option?
79 changes: 79 additions & 0 deletions src/typeahead/test/typeahead.spec.js
Expand Up @@ -967,6 +967,85 @@ describe('typeahead tests', function() {
expect($scope.result).toEqual($scope.states[0]);
});
});

describe('input hint', function() {
var element;

beforeEach(function() {
element = prepareInputEl('<div><input ng-model="result" uib-typeahead="state.name for state in states| filter:$viewValue" typeahead-show-hint="true"></div>');
});

it('should show hint when input matches first match', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');
});

it('should not show hint when input does not match first match', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'las');
expect(hintEl.val()).toEqual('');
});

it('should reset hint when a match is clicked', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');

var match = findMatches(element).find('a').eq(0);
match.click();
$scope.$digest();
expect(hintEl.val()).toEqual('');
});

it('should reset hint when click outside', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');

$document.find('body').click();
$scope.$digest();
expect(hintEl.val()).toEqual('');
});

it('should reset hint on enter', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');
triggerKeyDown(element, 13);
expect(hintEl.val()).toEqual('');
});

it('should reset hint on tab', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');
triggerKeyDown(element, 9);
expect(hintEl.val()).toEqual('');
});

it('should reset hint on escape key', function() {
var hintEl = findInput(element);

expect(hintEl.val()).toEqual('');
changeInputValueTo(element, 'Alas');
expect(hintEl.val()).toEqual('Alaska');
triggerKeyDown(element, 27);
expect(hintEl.val()).toEqual('');
});
});

describe('append to element id', function() {
it('append typeahead results to element', function() {
Expand Down
50 changes: 50 additions & 0 deletions src/typeahead/typeahead.js
Expand Up @@ -71,6 +71,8 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
//binding to a variable that indicates if dropdown is open
var isOpenSetter = $parse(attrs.typeaheadIsOpen).assign || angular.noop;

var showHint = originalScope.$eval(attrs.typeaheadShowHint) || false;

//INTERNAL VARIABLES

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

//add read-only input to show hint
if (showHint) {
var inputsContainer = angular.element('<div></div>');
inputsContainer.css('position', 'relative');
element.after(inputsContainer);
var hintInputElem = element.clone();
hintInputElem.attr('placeholder', '');
hintInputElem.val('');
hintInputElem.css({
'position': 'absolute',
'top': '0px',
'left': '0px',
'border-color': 'transparent',
'box-shadow': 'none',
'opacity': 1,
'background': 'none 0% 0% / auto repeat scroll padding-box border-box rgb(255, 255, 255)',
'color': '#999'
});
element.css({
'position': 'relative',
'vertical-align': 'top',
'background-color': 'transparent'
});
inputsContainer.append(hintInputElem);
hintInputElem.after(element);
}

//pop-up element used to display matches
var popUpEl = angular.element('<div uib-typeahead-popup></div>');
popUpEl.attr({
Expand All @@ -132,10 +161,17 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
popUpEl.attr('popup-template-url', attrs.typeaheadPopupTemplateUrl);
}

var resetHint = function() {
if (showHint) {
hintInputElem.val('');
}
};

var resetMatches = function() {
scope.matches = [];
scope.activeIdx = -1;
element.attr('aria-expanded', false);
resetHint();
};

var getMatchId = function(index) {
Expand Down Expand Up @@ -196,6 +232,16 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
if (selectOnExact && scope.matches.length === 1 && inputIsExactMatch(inputValue, 0)) {
scope.select(0);
}

if (showHint) {
var firstLabel = scope.matches[0].label;
if (inputValue.length > 0 && firstLabel.slice(0, inputValue.length).toUpperCase() === inputValue.toUpperCase()) {
hintInputElem.val(inputValue + firstLabel.slice(inputValue.length));
}
else {
hintInputElem.val('');
}
}
} else {
resetMatches();
isNoResultsSetter(originalScope, true);
Expand Down Expand Up @@ -377,6 +423,10 @@ angular.module('ui.bootstrap.typeahead', ['ui.bootstrap.position'])
}
// Prevent jQuery cache memory leak
popUpEl.remove();

if (showHint) {
inputsContainer.remove();
}
});

var $popup = $compile(popUpEl)(scope);
Expand Down

0 comments on commit ef82ad1

Please sign in to comment.