Поддержка ',' в input type = "number" в IE 11 с использованием Angular + материал - PullRequest
0 голосов
/ 16 марта 2019

Мне нужно принять 10,12 в качестве действительного числа в IE 11, потому что у пользователей есть только немецкая цифровая клавиатура в качестве устройства ввода, у которой в качестве десятичного разделителя используется только запятая.

Текущее поведение (вход пользователя -> используемое значение)

10,12 -> 10

желаемое поведение

10,12 -> 10.12
10.12 -> 10.12

Как в настоящее время в Chrome.

Моя текущая идея состоит в том, чтобы реализовать ControlValueAccessor для ввода type = "text", который анализирует значение в число, но мои попытки этого не увенчались успехом.

Ввод должен работать с реактивными формами и должен иметь числовое значение

1 Ответ

2 голосов
/ 16 марта 2019

Я столкнулся с той же проблемой и не смог получить запятую в качестве десятичного разделителя с <input type="number"> даже при установке языка в другой языковой стандарт и использовании шага меньше 1:

<input type="number" step="0.01" lang="en-US">

Поэтому я выбрал нестандартное решение на основе <input type="text"> с настраиваемым механизмом фильтрации, разрешающим ввод только чисел.

См. этот стек для полной демонстрации.

Наиболее важной частью является фильтрация того, что пользователь вводит в поле. Я предлагаю вам написать директиву, которая прослушивает события ввода / нажатия клавиш / вставки и использует регулярное выражение, чтобы разрешить только числа с плавающей запятой / целые числа.

Следующее регулярное выражение (/^-?\d*(,|\.)?\d*$/) позволяет числу начинаться с необязательного -, за которым следуют цифры, затем запятая или точка и другие цифры.

Если новое значение (текущее значение + нажатая клавиша) не соответствует регулярному выражению, просто предотвратите событие с event.preventDefault(). В противном случае ничего не делать и позволить значению поступить на вход.

Обратите внимание, что вам также необходимо позаботиться о специальных клавишах копирования / вырезания / вставки / отмены / повторения. А также принять во внимание положение курсора и выбор, если таковые имеются.

Как только фильтрация завершена, вы можете реализовать интерфейс ControlValueAccessor и привязать его к своему входу через события change/input. Выполните преобразование строки в число в этих обработчиках и выполните преобразование числа в строку в получателе или в канале, который вы связываете с атрибутом 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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...