I have the following use case:
There is an input
and a popup should be displayed next to the input whenever it has focus. So while the input
has focus, the user can either type into the input
or use the popup to select something.
When the user is done, he can unfocus the element either by clicking somewhere else or by pressing tab and focussing the next element. However, if he clicks a button inside the popup, the popup should stay open and the input should stay focused so that the user can continue typing.
The problem that I have is that angular processes (blur) on the input before it processes (click)
in the popup. That means that when the user
clicks in the popup, the input loses focus, it gets hidden, and then the
click of the user won't be processed anymore.
I have made a stackblitz-demo for the problem:
This is the source code:
app.component.html
<h1>Hello</h1>
<p>
Type 'one', 'two' or 'three' to change number or select a number
from popup.
<br>
<input type="checkbox" [(ngModel)]="hideHelperOnBlur" /> Hide
helper on blur (if this is set, then the buttons in the popup don't work
but if this is not set, the popup doesn't close if the input looses
focus -> how can I get both? The buttons to work but the popup still
closing on blur?)
</p>
Your number: {{selectedNumber}}
<p>
Change number:
<input [ngModel]="formattedNumber" (ngModelChange)="newNumberTyped($event)" (focus)="helperVisible = true"
(blur)="processBlur()" #mainInput />
<div *ngIf="helperVisible">
<button *ngFor="let number of numbers" (click)="selectNumber(number.raw)">{{number.formatted}} </button>
</div>
app.component.ts
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular';
formattedNumber: string = 'three';
selectedNumber: number = 3;
helperVisible: boolean = false;
numbers: any[] = [
{ raw: 1, formatted: 'one' },
{ raw: 2, formatted: 'two' },
{ raw: 3, formatted: 'three' }
];
@ViewChild('mainInput', { static: false })
mainInput: ElementRef;
hideHelperOnBlur: boolean;
newNumberTyped(newFormattedNumber: string) {
this.numbers.forEach((number) => {
if (newFormattedNumber == number.formatted) {
this.formattedNumber = newFormattedNumber;
this.selectedNumber = number.raw;
}
});
}
selectNumber(newRawNumber: number) {
this.numbers.forEach((number) => {
if (newRawNumber == number.raw) {
this.formattedNumber = number.formatted;
this.selectedNumber = newRawNumber;
}
});
this.mainInput.nativeElement.focus();
}
processBlur() {
if (this.hideHelperOnBlur) {
this.helperVisible = false;
}
}
}
I expect the following behavior:
I only seem to be able to get either the second and third or the fourth criteria to work (see the checkbox in the demo).
What I already tried:
Does anyone have a solution?
This is more of a Javascript thing. The blur
event takes place before the click
event. The click
event only takes place once the mouse button is released.
You can use the mousedown
event here to your advantage. The mousedown
event takes place before the blur
event. Simply call preventDefault
on mousedown
in the popover buttons to prevent the input
from losing focus. This would also solve the issue where your input
blinks when you click buttons in the popover so you can get rid of this.mainInput.nativeElement.focus();
in your selectNumber
function.
<button *ngFor="let number of numbers" (mousedown)="$event.preventDefault()" (click)="selectNumber(number.raw)">{{number.formatted}}</button>
Here is a working example on StackBlitz.