From 0ca851524f400d3efe803ad469ebf259402d4d1d Mon Sep 17 00:00:00 2001 From: crisbeto Date: Sun, 19 Feb 2017 15:18:39 +0100 Subject: [PATCH] fix(autocomplete): opening on load in IE 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. --- src/lib/autocomplete/autocomplete-trigger.ts | 13 ++-- src/lib/autocomplete/autocomplete.spec.ts | 70 ++++++++++++-------- 2 files changed, 50 insertions(+), 33 deletions(-) diff --git a/src/lib/autocomplete/autocomplete-trigger.ts b/src/lib/autocomplete/autocomplete-trigger.ts index b6e841046605..bf7effb8b501 100644 --- a/src/lib/autocomplete/autocomplete-trigger.ts +++ b/src/lib/autocomplete/autocomplete-trigger.ts @@ -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] @@ -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 { diff --git a/src/lib/autocomplete/autocomplete.spec.ts b/src/lib/autocomplete/autocomplete.spec.ts index e533ae34704c..5baee70ad18e 100644 --- a/src/lib/autocomplete/autocomplete.spec.ts +++ b/src/lib/autocomplete/autocomplete.spec.ts @@ -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 = @@ -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(() => { @@ -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(() => { @@ -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', () => { @@ -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) @@ -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) @@ -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(() => { @@ -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) @@ -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); @@ -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; @@ -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) @@ -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; @@ -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 = @@ -826,7 +825,7 @@ describe('MdAutocomplete', () => { - + {{ state.code }}: {{ state.name }} @@ -881,10 +880,10 @@ class SimpleAutocomplete implements OnDestroy { @Component({ template: ` - - + {{ state }} @@ -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) {}