Skip to content

Commit

Permalink
fix(autocomplete): prevent opening on load in IE (#3190)
Browse files Browse the repository at this point in the history
Prevents the autocomplete being opened on load in IE. This was due to a bug where IE fires the `input` event on `load`, `focus` and `blur`, if the input element has a placeholder.

Fixes #3183.
  • Loading branch information
crisbeto authored and mmalerba committed Feb 23, 2017
1 parent d744a5f commit a4da08b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 33 deletions.
13 changes: 9 additions & 4 deletions src/lib/autocomplete/autocomplete-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const MD_AUTOCOMPLETE_VALUE_ACCESSOR: any = {
'[attr.aria-owns]': 'autocomplete?.id',
'(focus)': 'openPanel()',
'(blur)': '_handleBlur($event.relatedTarget?.tagName)',
'(input)': '_handleInput($event.target.value)',
'(input)': '_handleInput($event)',
'(keydown)': '_handleKeydown($event)',
},
providers: [MD_AUTOCOMPLETE_VALUE_ACCESSOR]
Expand Down Expand Up @@ -213,9 +213,14 @@ export class MdAutocompleteTrigger implements AfterContentInit, ControlValueAcce
}
}

_handleInput(value: string): void {
this._onChange(value);
this.openPanel();
_handleInput(event: KeyboardEvent): void {
// We need to ensure that the input is focused, because IE will fire the `input`
// event on focus/blur/load if the input has a placeholder. See:
// https://connect.microsoft.com/IE/feedback/details/885747/
if (document.activeElement === event.target) {
this._onChange((event.target as HTMLInputElement).value);
this.openPanel();
}
}

_handleBlur(newlyFocusedTag: string): void {
Expand Down
70 changes: 41 additions & 29 deletions src/lib/autocomplete/autocomplete.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,7 @@ describe('MdAutocomplete', () => {

fixture.whenStable().then(() => {
// Filter down the option list to a subset of original options ('Alabama', 'California')
input.value = 'al';
dispatchEvent('input', input);
typeInElement('al', input);
fixture.detectChanges();

let options =
Expand All @@ -134,8 +133,7 @@ describe('MdAutocomplete', () => {

// Changing value from 'Alabama' to 'al' to re-populate the option list,
// ensuring that 'California' is created new.
input.value = 'al';
dispatchEvent('input', input);
typeInElement('al', input);
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -177,8 +175,7 @@ describe('MdAutocomplete', () => {
.toContain('mat-autocomplete-visible', `Expected panel to start out visible.`);

// Filter down the option list such that no options match the value
input.value = 'af';
dispatchEvent('input', input);
typeInElement('af', input);
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -210,6 +207,18 @@ describe('MdAutocomplete', () => {
});
}));

it('should not open the panel when the `input` event is invoked on a non-focused input', () => {
expect(fixture.componentInstance.trigger.panelOpen)
.toBe(false, `Expected panel state to start out closed.`);

input.value = 'Alabama';
dispatchEvent('input', input);
fixture.detectChanges();

expect(fixture.componentInstance.trigger.panelOpen)
.toBe(false, `Expected panel state to stay closed.`);
});

});

it('should have the correct text direction in RTL', () => {
Expand Down Expand Up @@ -241,15 +250,13 @@ describe('MdAutocomplete', () => {
fixture.componentInstance.trigger.openPanel();
fixture.detectChanges();

input.value = 'a';
dispatchEvent('input', input);
typeInElement('a', input);
fixture.detectChanges();

expect(fixture.componentInstance.stateCtrl.value)
.toEqual('a', 'Expected control value to be updated as user types.');

input.value = 'al';
dispatchEvent('input', input);
typeInElement('al', input);
fixture.detectChanges();

expect(fixture.componentInstance.stateCtrl.value)
Expand Down Expand Up @@ -282,8 +289,7 @@ describe('MdAutocomplete', () => {
options[1].click();
fixture.detectChanges();

input.value = 'Californi';
dispatchEvent('input', input);
typeInElement('Californi', input);
fixture.detectChanges();

expect(fixture.componentInstance.stateCtrl.value)
Expand Down Expand Up @@ -339,8 +345,7 @@ describe('MdAutocomplete', () => {
}));

it('should clear the text field if value is reset programmatically', async(() => {
input.value = 'Alabama';
dispatchEvent('input', input);
typeInElement('Alabama', input);
fixture.detectChanges();

fixture.whenStable().then(() => {
Expand Down Expand Up @@ -376,8 +381,7 @@ describe('MdAutocomplete', () => {
expect(fixture.componentInstance.stateCtrl.dirty)
.toBe(false, `Expected control to start out pristine.`);

input.value = 'a';
dispatchEvent('input', input);
typeInElement('a', input);
fixture.detectChanges();

expect(fixture.componentInstance.stateCtrl.dirty)
Expand Down Expand Up @@ -531,8 +535,7 @@ describe('MdAutocomplete', () => {
fixture.detectChanges();

fixture.whenStable().then(() => {
input.value = 'o';
dispatchEvent('input', input);
typeInElement('o', input);
fixture.detectChanges();

fixture.componentInstance.trigger._handleKeydown(DOWN_ARROW_EVENT);
Expand Down Expand Up @@ -565,8 +568,7 @@ describe('MdAutocomplete', () => {

it('should fill the text field, not select an option, when SPACE is entered', async(() => {
fixture.whenStable().then(() => {
input.value = 'New';
dispatchEvent('input', input);
typeInElement('New', input);
fixture.detectChanges();

const SPACE_EVENT = new FakeKeyboardEvent(SPACE) as KeyboardEvent;
Expand Down Expand Up @@ -604,8 +606,7 @@ describe('MdAutocomplete', () => {
expect(overlayContainerElement.textContent)
.toEqual('', `Expected panel to close after ENTER key.`);

input.value = 'Alabam';
dispatchEvent('input', input);
typeInElement('Alabama', input);
fixture.detectChanges();

expect(fixture.componentInstance.trigger.panelOpen)
Expand Down Expand Up @@ -782,8 +783,7 @@ describe('MdAutocomplete', () => {
fixture.detectChanges();

fixture.whenStable().then(() => {
input.value = 'f';
dispatchEvent('input', input);
typeInElement('f', input);
fixture.detectChanges();

const inputTop = input.getBoundingClientRect().top;
Expand All @@ -808,8 +808,7 @@ describe('MdAutocomplete', () => {
fixture.detectChanges();

const input = fixture.debugElement.query(By.css('input')).nativeElement;
input.value = 'd';
dispatchEvent('input', input);
typeInElement('d', input);
fixture.detectChanges();

const options =
Expand All @@ -826,7 +825,7 @@ describe('MdAutocomplete', () => {
<md-input-container>
<input mdInput placeholder="State" [mdAutocomplete]="auto" [formControl]="stateCtrl">
</md-input-container>
<md-autocomplete #auto="mdAutocomplete" [displayWith]="displayFn">
<md-option *ngFor="let state of filteredStates" [value]="state">
<span> {{ state.code }}: {{ state.name }} </span>
Expand Down Expand Up @@ -881,10 +880,10 @@ class SimpleAutocomplete implements OnDestroy {
@Component({
template: `
<md-input-container>
<input mdInput placeholder="State" [mdAutocomplete]="auto"
<input mdInput placeholder="State" [mdAutocomplete]="auto"
(input)="onInput($event.target?.value)">
</md-input-container>
<md-autocomplete #auto="mdAutocomplete">
<md-option *ngFor="let state of filteredStates" [value]="state">
<span> {{ state }} </span>
Expand Down Expand Up @@ -920,6 +919,19 @@ function dispatchEvent(eventName: string, element: HTMLElement): void {
element.dispatchEvent(event);
}


/**
* Focuses an input, sets its value and dispatches
* the `input` event, simulating the user typing.
* @param value Value to be set on the input.
* @param element Element onto which to set the value.
*/
function typeInElement(value: string, element: HTMLInputElement) {
element.focus();
element.value = value;
dispatchEvent('input', element);
}

/** This is a mock keyboard event to test keyboard events in the autocomplete. */
class FakeKeyboardEvent {
constructor(public keyCode: number) {}
Expand Down

0 comments on commit a4da08b

Please sign in to comment.