thingsboard/ui-ngx/src/app/shared/components/phone-input.component.ts

287 lines
8.7 KiB
TypeScript
Raw Normal View History

///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import {
ControlValueAccessor,
FormBuilder,
FormControl,
FormGroup,
NG_VALIDATORS,
NG_VALUE_ACCESSOR,
ValidationErrors,
Validator,
ValidatorFn,
Validators
} from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { Country, CountryData } from '@shared/models/country.models';
import examples from 'libphonenumber-js/examples.mobile.json';
import { Subscription } from 'rxjs';
import { FloatLabelType, MatFormFieldAppearance } from '@angular/material/form-field/form-field';
@Component({
selector: 'tb-phone-input',
templateUrl: './phone-input.component.html',
styleUrls: ['./phone-input.component.scss'],
providers: [
CountryData,
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => PhoneInputComponent),
multi: true
}
]
})
export class PhoneInputComponent implements OnInit, ControlValueAccessor, Validator {
@Input()
disabled: boolean;
@Input()
2022-06-24 10:32:54 +03:00
defaultCountry = 'US';
@Input()
enableFlagsSelect = true;
@Input()
required = true;
@Input()
floatLabel: FloatLabelType = 'auto';
@Input()
appearance: MatFormFieldAppearance = 'legacy';
@Input()
placeholder;
@Input()
label = 'phone-input.phone-input-label';
2022-05-30 14:54:41 +03:00
2022-07-18 12:06:05 +03:00
get showFlagSelect(): boolean {
return this.enableFlagsSelect && !this.isLegacy;
}
2022-06-13 16:37:40 +03:00
allCountries: Array<Country> = this.countryCodeData.allCountries;
2022-06-24 10:32:54 +03:00
phonePlaceholder = '+12015550123';
flagIcon: string;
2022-06-13 16:37:40 +03:00
phoneFormGroup: FormGroup;
2022-06-24 10:32:54 +03:00
private isLoading = true;
get isLoad(): boolean {
return this.isLoading;
}
set isLoad(value) {
if (this.isLoading) {
this.isLoading = value;
if (this.defaultCountry) {
this.getFlagAndPhoneNumberData(this.defaultCountry);
}
2022-11-02 17:23:33 +02:00
if (this.phoneFormGroup && this.phoneFormGroup.get('phoneNumber').value) {
const parsedPhoneNumber = this.parsePhoneNumberFromString(this.phoneFormGroup.get('phoneNumber').value);
this.defineCountryFromNumber(parsedPhoneNumber);
2022-06-24 10:32:54 +03:00
}
}
}
2022-07-18 12:06:05 +03:00
private isLegacy = false;
2022-06-24 10:32:54 +03:00
private getExampleNumber;
private parsePhoneNumberFromString;
2022-06-13 16:37:40 +03:00
private baseCode = 127397;
2022-06-24 10:32:54 +03:00
private countryCallingCode = '+';
private modelValue: string;
2022-07-06 18:54:34 +03:00
private changeSubscriptions: Subscription[] = [];
private validators: ValidatorFn[] = [this.validatePhoneNumber()];
2022-07-06 18:54:34 +03:00
private propagateChange = (v: any) => { };
constructor(private translate: TranslateService,
private fb: FormBuilder,
private countryCodeData: CountryData) {
2022-06-24 10:32:54 +03:00
import('libphonenumber-js/max').then((libphonenubmer) => {
this.parsePhoneNumberFromString = libphonenubmer.parsePhoneNumberFromString;
this.getExampleNumber = libphonenubmer.getExampleNumber;
}).then(() => this.isLoad = false);
}
ngOnInit(): void {
2022-06-13 16:37:40 +03:00
if (this.required) {
2022-07-18 11:40:49 +03:00
this.validators.push(Validators.required);
}
this.phoneFormGroup = this.fb.group({
country: [null, []],
2022-07-18 11:40:49 +03:00
phoneNumber: [null, this.validators]
});
2022-07-06 18:54:34 +03:00
this.changeSubscriptions.push(this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => {
2022-11-02 17:23:33 +02:00
let parsedPhoneNumber = null;
if (value && this.parsePhoneNumberFromString) {
parsedPhoneNumber = this.parsePhoneNumberFromString(value);
this.defineCountryFromNumber(parsedPhoneNumber);
}
this.updateModel(parsedPhoneNumber);
2022-07-06 18:54:34 +03:00
}));
2022-07-06 18:54:34 +03:00
this.changeSubscriptions.push(this.phoneFormGroup.get('country').valueChanges.subscribe(value => {
if (value) {
const code = this.countryCallingCode;
this.getFlagAndPhoneNumberData(value);
let phoneNumber = this.phoneFormGroup.get('phoneNumber').value;
2022-05-30 14:54:41 +03:00
if (phoneNumber) {
if (code !== '+' && code !== this.countryCallingCode && phoneNumber.includes(code)) {
2022-05-30 14:54:41 +03:00
phoneNumber = phoneNumber.replace(code, this.countryCallingCode);
2022-06-13 16:37:40 +03:00
this.phoneFormGroup.get('phoneNumber').patchValue(phoneNumber);
2022-05-30 14:54:41 +03:00
}
}
}
2022-07-06 18:54:34 +03:00
}));
}
ngOnDestroy() {
2022-07-06 18:54:34 +03:00
for (const subscription of this.changeSubscriptions) {
subscription.unsubscribe();
}
}
2022-05-30 14:54:41 +03:00
focus() {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
if (!phoneNumber.value) {
2022-07-18 11:40:49 +03:00
phoneNumber.patchValue(this.countryCallingCode, {emitEvent: true});
2022-05-30 14:54:41 +03:00
}
}
2022-06-13 16:37:40 +03:00
private getFlagAndPhoneNumberData(country) {
if (this.enableFlagsSelect) {
this.flagIcon = this.getFlagIcon(country);
}
this.getPhoneNumberData(country);
}
2022-06-13 16:37:40 +03:00
private getPhoneNumberData(country): void {
2022-06-24 10:32:54 +03:00
if (this.getExampleNumber) {
const phoneData = this.getExampleNumber(country, examples);
this.phonePlaceholder = phoneData.number;
this.countryCallingCode = `+${this.enableFlagsSelect ? phoneData.countryCallingCode : ''}`;
}
}
2022-06-13 16:37:40 +03:00
private getFlagIcon(countryCode) {
return String.fromCodePoint(...countryCode.split('').map(country => this.baseCode + country.charCodeAt(0)));
}
2022-11-02 17:23:33 +02:00
private updateModelValueInFormat(parsedPhoneNumber: any) {
this.modelValue = parsedPhoneNumber.format('E.164');
}
validatePhoneNumber(): ValidatorFn {
return (c: FormControl) => {
const phoneNumber = c.value;
2022-07-14 18:13:47 +03:00
if (phoneNumber && this.parsePhoneNumberFromString) {
2022-06-24 10:32:54 +03:00
const parsedPhoneNumber = this.parsePhoneNumberFromString(phoneNumber);
if (!parsedPhoneNumber?.isValid() || !parsedPhoneNumber?.isPossible()) {
return {
invalidPhoneNumber: {
valid: false
}
};
}
}
return null;
};
}
2022-11-02 17:23:33 +02:00
private defineCountryFromNumber(parsedPhoneNumber) {
const country = this.phoneFormGroup.get('country').value;
if (parsedPhoneNumber?.country && parsedPhoneNumber?.country !== country) {
this.phoneFormGroup.get('country').patchValue(parsedPhoneNumber.country, {emitEvent: true});
2022-06-24 10:32:54 +03:00
}
}
validate(): ValidationErrors | null {
2022-07-06 18:54:34 +03:00
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
2022-07-18 11:40:49 +03:00
return phoneNumber.valid || this.countryCallingCode === phoneNumber.value ? null : {
phoneFormGroup: false
};
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
if (isDisabled) {
this.phoneFormGroup.disable({emitEvent: false});
} else {
this.phoneFormGroup.enable({emitEvent: false});
}
}
writeValue(phoneNumber): void {
this.modelValue = phoneNumber;
2022-06-24 10:32:54 +03:00
let country = this.defaultCountry;
if (this.parsePhoneNumberFromString) {
2022-07-18 11:40:49 +03:00
this.phoneFormGroup.get('phoneNumber').clearValidators();
this.phoneFormGroup.get('phoneNumber').setValidators(this.validators);
if (phoneNumber) {
const parsedPhoneNumber = this.parsePhoneNumberFromString(phoneNumber);
if (parsedPhoneNumber?.isValid() && parsedPhoneNumber?.isPossible()) {
country = parsedPhoneNumber?.country || this.defaultCountry;
2022-11-02 17:23:33 +02:00
this.updateModelValueInFormat(parsedPhoneNumber);
2022-07-18 12:06:05 +03:00
this.isLegacy = false;
2022-07-18 11:40:49 +03:00
} else {
const validators = [Validators.maxLength(255)];
if (this.required) {
validators.push(Validators.required);
}
this.phoneFormGroup.get('phoneNumber').setValidators(validators);
2022-07-18 12:06:05 +03:00
this.isLegacy = true;
2022-07-18 11:40:49 +03:00
}
} else {
2022-07-18 12:06:05 +03:00
this.isLegacy = false;
2022-07-18 11:40:49 +03:00
}
this.phoneFormGroup.updateValueAndValidity({emitEvent: false});
2022-06-24 10:32:54 +03:00
this.getFlagAndPhoneNumberData(country);
}
2022-07-06 18:54:34 +03:00
this.phoneFormGroup.reset({phoneNumber, country}, {emitEvent: false});
}
2022-11-02 17:23:33 +02:00
private updateModel(parsedPhoneNumber?) {
const phoneNumber = this.phoneFormGroup.get('phoneNumber');
2022-07-18 11:40:49 +03:00
if (phoneNumber.value === '+' || phoneNumber.value === this.countryCallingCode) {
this.propagateChange(null);
} else if (phoneNumber.valid) {
2022-12-29 15:12:34 +02:00
this.modelValue = phoneNumber.value;
2022-11-02 17:23:33 +02:00
if (parsedPhoneNumber) {
this.updateModelValueInFormat(parsedPhoneNumber);
}
this.propagateChange(this.modelValue);
2022-07-06 18:54:34 +03:00
} else {
this.propagateChange(null);
}
}
}