在 IE 11 中使用 Angular + material 支持 ',' in input type="number"
Support for ',' in input type="number" in IE 11 using Angular + material
我需要接受 10,12 作为 IE 11 中的有效数字,因为用户只有德语数字键盘作为输入设备,只有逗号作为小数点分隔符。
当前行为是(用户输入 -> 使用的值)
10,12 -> 10
期望的行为是
10,12 -> 10.12
10.12 -> 10.12
因为它目前在 Chrome。
我目前的想法是为输入类型实现一个 ControlValueAccessor="text",它将值解析为一个数字,但我的尝试失败了。
输入必须使用 reactiveForms,并且必须是数值
我遇到了同样的问题,即使将语言设置为不同的语言环境并使用小于 1 的步长,也无法使用 <input type="number">
获得逗号作为小数点分隔符:
<input type="number" step="0.01" lang="en-US">
So I opted for a custom solution based on <input type="text">
with a custom filtering mechanism to only allow numbers in.
See this stackblitz for a complete demo.
最重要的部分是过滤用户在字段中输入的内容。我建议您编写一个指令来侦听 input/keydown/paste 事件并使用正则表达式仅允许 float/integer 数字。
以下正则表达式 (/^-?\d*(,|\.)?\d*$/
) 允许数字以可选的 -
开头,后跟数字,然后是逗号或点以及更多数字。
如果新值(当前值 + 按下的键)与正则表达式不匹配,只需使用 event.preventDefault()
阻止事件发生。否则,什么也不做,让值进入输入。
请注意,您还必须注意 copy/cut/paste/undo/redo 特殊键。还要考虑光标位置和选择(如果有的话)。
过滤完成后,您可以实现 ControlValueAccessor
接口并通过其 change/input
事件将其绑定到您的输入。在这些处理程序中执行字符串到数字的转换,并在 getter 或绑定到 value
属性的管道中执行数字到字符串的转换。
这是此类指令的示例,您可以通过将正则表达式作为输入参数来概括它。
import { Directive, Input, HostListener, forwardRef } from '@angular/core';
@Directive({
selector: '[appNumberOnly]'
})
export class NumberOnlyDirective {
@HostListener('keydown', ['$event'])
public onKeydown(event: KeyboardEvent): void {
const { key } = event;
if (this.isSpecialOperation(event) || !this.isKeyPrintable(event)) {
return;
}
const newValue = this.getNewValue(event.target as HTMLInputElement, key);
if (!this.valueIsValid(newValue)) {
event.preventDefault();
}
}
@HostListener('paste', ['$event'])
public onPaste(event: ClipboardEvent): void {
const pastedText = event.clipboardData.getData('text');
const newValue = this.getNewValue(event.target as HTMLInputElement, pastedText);
if (!this.valueIsValid(newValue)) {
event.preventDefault();
}
}
private getNewValue(target: HTMLInputElement, str: string): string {
const { value = '', selectionStart, selectionEnd } = target;
return [
...value.split('').splice(0, selectionStart),
str,
...value.split('').splice(selectionEnd)].join('');
}
private valueIsValid(value: string): boolean {
return /^-?\d*(,|\.)?\d*$/.test(value);
}
private isSpecialOperation(event: KeyboardEvent): boolean {
const { keyCode, ctrlKey, metaKey } = event;
// allow ctr-A/C/V/X/Y/Z
const keysACVXYZ = [65, 67, 86, 88, 89, 90];
if ((ctrlKey || metaKey) && keysACVXYZ.indexOf(keyCode) >= 0) {
return true;
}
return false;
}
private isKeyPrintable(event: KeyboardEvent): boolean {
const { keyCode } = event;
return (
(keyCode > 47 && keyCode < 58) || // number keys
keyCode === 32 || keyCode === 13 || // spacebar & return key(s)
(keyCode > 64 && keyCode < 91) || // letter keys
(keyCode > 95 && keyCode < 112) || // numpad keys
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
(keyCode > 218 && keyCode < 223)); // [\]' (in order)
}
}
以及实现 ControlValueAccessor
:
的自定义输入数字组件
import { Component, ViewChild, forwardRef, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'app-input-number',
template: `
<input
type="text"
#input
appNumberOnly
[placeholder]="placeholder"
[value]="_stringifiedValue"
(input)="_onInput($event.target.value)"
(change)="_onChange($event.target.value)"
(blur)="input.value = _stringifiedValue">
`,
styles: [`
:host { width: 100%; display: block; }
input { width: 100%; }
`],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputNumberComponent),
multi: true
}
]
})
export class InputNumberComponent implements ControlValueAccessor {
private onChange = [(_: number) => {}];
private onTouch = [() => {}];
@Input() placeholder;
@ViewChild('input') _input: ElementRef;
@Input()
get value(): number {
return this._value;
}
set value(value: number) {
const safeValue = this.safeValue(value);
if (safeValue !== this._value) {
this._value = safeValue;
this.onChange.forEach(fn => fn(safeValue));
}
}
private _value: number = undefined;
@Output() valueChange = new EventEmitter<number>();
get _stringifiedValue(): string {
const val = (this._input.nativeElement.value || '').replace('.', ',');
if (val === '-' || val === ',') return val;
const safeValue = this.safeValue(this.value);
return this.stringify(safeValue).replace('.', ',');
}
_onInput(value: string): void {
this.value = this.safeValue(value);
}
_onChange(value: string): void {
this.value = this.safeValue(value);
this.valueChange.emit(this.value);
}
private safeValue(val: string | number): number {
const safeValue = parseFloat(this.stringify(val).replace(',', '.'));
return isNaN(safeValue) ? undefined : safeValue;
}
private stringify(val: string | number): string {
return val === undefined || val === null ? '' : `${val}`;
}
public registerOnChange(fn: any): void {
this.onChange.push(fn);
}
public registerOnTouched(fn: any): void {
this.onTouch.push(fn);
}
public writeValue(inputValue: number): void {
this.value = this.safeValue(inputValue);
}
}
然后可以使用 [(ngModel)]
或 [(value)]
的双向绑定来使用该组件。它也适用于反应形式:
<app-input-number [(ngModel)]="value"></app-input-number>
<app-input-number [(value)]="value"></app-input-number>
我需要接受 10,12 作为 IE 11 中的有效数字,因为用户只有德语数字键盘作为输入设备,只有逗号作为小数点分隔符。
当前行为是(用户输入 -> 使用的值)
10,12 -> 10
期望的行为是
10,12 -> 10.12
10.12 -> 10.12
因为它目前在 Chrome。
我目前的想法是为输入类型实现一个 ControlValueAccessor="text",它将值解析为一个数字,但我的尝试失败了。
输入必须使用 reactiveForms,并且必须是数值
我遇到了同样的问题,即使将语言设置为不同的语言环境并使用小于 1 的步长,也无法使用 <input type="number">
获得逗号作为小数点分隔符:
<input type="number" step="0.01" lang="en-US">
So I opted for a custom solution based on
<input type="text">
with a custom filtering mechanism to only allow numbers in.See this stackblitz for a complete demo.
最重要的部分是过滤用户在字段中输入的内容。我建议您编写一个指令来侦听 input/keydown/paste 事件并使用正则表达式仅允许 float/integer 数字。
以下正则表达式 (/^-?\d*(,|\.)?\d*$/
) 允许数字以可选的 -
开头,后跟数字,然后是逗号或点以及更多数字。
如果新值(当前值 + 按下的键)与正则表达式不匹配,只需使用 event.preventDefault()
阻止事件发生。否则,什么也不做,让值进入输入。
请注意,您还必须注意 copy/cut/paste/undo/redo 特殊键。还要考虑光标位置和选择(如果有的话)。
过滤完成后,您可以实现 ControlValueAccessor
接口并通过其 change/input
事件将其绑定到您的输入。在这些处理程序中执行字符串到数字的转换,并在 getter 或绑定到 value
属性的管道中执行数字到字符串的转换。
这是此类指令的示例,您可以通过将正则表达式作为输入参数来概括它。
import { Directive, Input, HostListener, forwardRef } from '@angular/core';
@Directive({
selector: '[appNumberOnly]'
})
export class NumberOnlyDirective {
@HostListener('keydown', ['$event'])
public onKeydown(event: KeyboardEvent): void {
const { key } = event;
if (this.isSpecialOperation(event) || !this.isKeyPrintable(event)) {
return;
}
const newValue = this.getNewValue(event.target as HTMLInputElement, key);
if (!this.valueIsValid(newValue)) {
event.preventDefault();
}
}
@HostListener('paste', ['$event'])
public onPaste(event: ClipboardEvent): void {
const pastedText = event.clipboardData.getData('text');
const newValue = this.getNewValue(event.target as HTMLInputElement, pastedText);
if (!this.valueIsValid(newValue)) {
event.preventDefault();
}
}
private getNewValue(target: HTMLInputElement, str: string): string {
const { value = '', selectionStart, selectionEnd } = target;
return [
...value.split('').splice(0, selectionStart),
str,
...value.split('').splice(selectionEnd)].join('');
}
private valueIsValid(value: string): boolean {
return /^-?\d*(,|\.)?\d*$/.test(value);
}
private isSpecialOperation(event: KeyboardEvent): boolean {
const { keyCode, ctrlKey, metaKey } = event;
// allow ctr-A/C/V/X/Y/Z
const keysACVXYZ = [65, 67, 86, 88, 89, 90];
if ((ctrlKey || metaKey) && keysACVXYZ.indexOf(keyCode) >= 0) {
return true;
}
return false;
}
private isKeyPrintable(event: KeyboardEvent): boolean {
const { keyCode } = event;
return (
(keyCode > 47 && keyCode < 58) || // number keys
keyCode === 32 || keyCode === 13 || // spacebar & return key(s)
(keyCode > 64 && keyCode < 91) || // letter keys
(keyCode > 95 && keyCode < 112) || // numpad keys
(keyCode > 185 && keyCode < 193) || // ;=,-./` (in order)
(keyCode > 218 && keyCode < 223)); // [\]' (in order)
}
}
以及实现 ControlValueAccessor
:
import { Component, ViewChild, forwardRef, ElementRef, Input, Output, EventEmitter } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';
@Component({
selector: 'app-input-number',
template: `
<input
type="text"
#input
appNumberOnly
[placeholder]="placeholder"
[value]="_stringifiedValue"
(input)="_onInput($event.target.value)"
(change)="_onChange($event.target.value)"
(blur)="input.value = _stringifiedValue">
`,
styles: [`
:host { width: 100%; display: block; }
input { width: 100%; }
`],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputNumberComponent),
multi: true
}
]
})
export class InputNumberComponent implements ControlValueAccessor {
private onChange = [(_: number) => {}];
private onTouch = [() => {}];
@Input() placeholder;
@ViewChild('input') _input: ElementRef;
@Input()
get value(): number {
return this._value;
}
set value(value: number) {
const safeValue = this.safeValue(value);
if (safeValue !== this._value) {
this._value = safeValue;
this.onChange.forEach(fn => fn(safeValue));
}
}
private _value: number = undefined;
@Output() valueChange = new EventEmitter<number>();
get _stringifiedValue(): string {
const val = (this._input.nativeElement.value || '').replace('.', ',');
if (val === '-' || val === ',') return val;
const safeValue = this.safeValue(this.value);
return this.stringify(safeValue).replace('.', ',');
}
_onInput(value: string): void {
this.value = this.safeValue(value);
}
_onChange(value: string): void {
this.value = this.safeValue(value);
this.valueChange.emit(this.value);
}
private safeValue(val: string | number): number {
const safeValue = parseFloat(this.stringify(val).replace(',', '.'));
return isNaN(safeValue) ? undefined : safeValue;
}
private stringify(val: string | number): string {
return val === undefined || val === null ? '' : `${val}`;
}
public registerOnChange(fn: any): void {
this.onChange.push(fn);
}
public registerOnTouched(fn: any): void {
this.onTouch.push(fn);
}
public writeValue(inputValue: number): void {
this.value = this.safeValue(inputValue);
}
}
然后可以使用 [(ngModel)]
或 [(value)]
的双向绑定来使用该组件。它也适用于反应形式:
<app-input-number [(ngModel)]="value"></app-input-number>
<app-input-number [(value)]="value"></app-input-number>