Localization of Input Type Number

html tag input of type=”number” and culture based decimal separator

Browsers do not support HTML5 number inputs seperators very well and each users experience would differ based on their computers regional settings. It may not be wise to force the seperator to be a comma.

The only way I can think of is to use javascript. Here is one example using normal input field. Unfortunately you will lose the html5 input attributes associated with number inputs.

var Inputs = document.querySelectorAll('input');for (var i=0; i<Inputs.length; i++) {  Inputs[i].onblur = function() {    this.value = this.value.replace('.',',');  }}
function multiplyAndPopulate() { var A1 = theForm.A1field.value.replace(',','.'); var A2 = theForm.A2field.value.replace(',','.'); var R1 = (A1*A2); if (isNaN(R1) == true) { alert('Invalid.'); return false; } else { theForm.R1field.value = R1; theForm.R1field.value = theForm.R1field.value.replace('.',','); }}
<input type="text" pattern="[0-9]+([\,][0-9]+)?" MaxLength="3"/>

Input type=number : Firefox converts floating point number to integer when there are 3 digit after a point

Your browser is using the french locale and french uses comma as decimal separator and period for thousands.

From the Mozilla documentation:

Localization

The allowed inputs for certain types depend on the locale. In some locales, 1,000.00 is a valid number, while in other locales the valid way to enter this number is 1.000,00.

Firefox uses the following heuristics to determine the locale to validate the user's input (at least for type="number"):

  • Try the language specified by a 'lang'/'xml:lang' attribute on the element or any of its parents;

  • Try the language specified by any Content-Language HTTP header or

  • If none specified, use the browser's locale.

If you want to change this behavior use:

<input lang="en" type="number">

Support for ',' in input type= number in IE 11 using Angular + material

I encountered the same problem and was not able to get a comma as the decimal separator with <input type="number"> even when setting the language to a different locale and using a step smaller than 1:

<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.

The most important part is filtering what the user inputs in the field. I suggest you write a directive that listens to input/keydown/paste events and that uses a regex to only allow float/integer numbers.

The following regex (/^-?\d*(,|\.)?\d*$/) allows a number to begin with an optional - followed by digits, then a comma or dot and more digits.

If the new value (current value + key pressed) does not match the regex, simply prevent the event from happening with event.preventDefault(). Otherwise, do nothing and let the value go to the input.

Note that you also have to take care of the copy/cut/paste/undo/redo special keys. And also take into account the cursor position and the selection if any.

Once the filtering is done, you can implement the ControlValueAccessor interface and bind it to your input via its change/input events. Do the string to number conversions in these handlers and do the number to string conversion in a getter or a pipe that you bind to the value attribute.

Here is an example of such a directive, you could generalize it by giving the regex as an input parameter.

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)
}
}

And a custom input-number component implementing 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);
}
}

The component can then be used with two-way binding with [(ngModel)] or with [(value)]. It will work with reactive-forms too:

<app-input-number [(ngModel)]="value"></app-input-number>
<app-input-number [(value)]="value"></app-input-number>


Related Topics



Leave a reply



Submit