committed by
GitHub
11 changed files with 372 additions and 360 deletions
@ -1,291 +0,0 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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.
|
|||
///
|
|||
|
|||
export const COUNTRIES = [ |
|||
'Afghanistan', |
|||
'Åland Islands', |
|||
'Albania', |
|||
'Algeria', |
|||
'American Samoa', |
|||
'Andorra', |
|||
'Angola', |
|||
'Anguilla', |
|||
'Antarctica', |
|||
'Antigua and Barbuda', |
|||
'Argentina', |
|||
'Armenia', |
|||
'Aruba', |
|||
'Australia', |
|||
'Austria', |
|||
'Azerbaijan', |
|||
'Bahamas', |
|||
'Bahrain', |
|||
'Bangladesh', |
|||
'Barbados', |
|||
'Belarus', |
|||
'Belgium', |
|||
'Belize', |
|||
'Benin', |
|||
'Bermuda', |
|||
'Bhutan', |
|||
'Bolivia', |
|||
'Bonaire, Sint Eustatius and Saba', |
|||
'Bosnia and Herzegovina', |
|||
'Botswana', |
|||
'Bouvet Island', |
|||
'Brazil', |
|||
'British Indian Ocean Territory', |
|||
'Brunei Darussalam', |
|||
'Bulgaria', |
|||
'Burkina Faso', |
|||
'Burundi', |
|||
'Cambodia', |
|||
'Cameroon', |
|||
'Canada', |
|||
'Cape Verde', |
|||
'Cayman Islands', |
|||
'Central African Republic', |
|||
'Chad', |
|||
'Chile', |
|||
'China', |
|||
'Christmas Island', |
|||
'Cocos (Keeling) Islands', |
|||
'Colombia', |
|||
'Comoros', |
|||
'Congo', |
|||
'Congo, The Democratic Republic of the', |
|||
'Cook Islands', |
|||
'Costa Rica', |
|||
'Côte d\'Ivoire', |
|||
'Croatia', |
|||
'Cuba', |
|||
'Curaçao', |
|||
'Cyprus', |
|||
'Czech Republic', |
|||
'Denmark', |
|||
'Djibouti', |
|||
'Dominica', |
|||
'Dominican Republic', |
|||
'Ecuador', |
|||
'Egypt', |
|||
'El Salvador', |
|||
'Equatorial Guinea', |
|||
'Eritrea', |
|||
'Estonia', |
|||
'Ethiopia', |
|||
'Falkland Islands (Malvinas)', |
|||
'Faroe Islands', |
|||
'Fiji', |
|||
'Finland', |
|||
'France', |
|||
'French Guiana', |
|||
'French Polynesia', |
|||
'French Southern Territories', |
|||
'Gabon', |
|||
'Gambia', |
|||
'Georgia', |
|||
'Germany', |
|||
'Ghana', |
|||
'Gibraltar', |
|||
'Greece', |
|||
'Greenland', |
|||
'Grenada', |
|||
'Guadeloupe', |
|||
'Guam', |
|||
'Guatemala', |
|||
'Guernsey', |
|||
'Guinea', |
|||
'Guinea-Bissau', |
|||
'Guyana', |
|||
'Haiti', |
|||
'Heard Island and McDonald Islands', |
|||
'Holy See (Vatican City State)', |
|||
'Honduras', |
|||
'Hong Kong', |
|||
'Hungary', |
|||
'Iceland', |
|||
'India', |
|||
'Indonesia', |
|||
'Iran, Islamic Republic of', |
|||
'Iraq', |
|||
'Ireland', |
|||
'Isle of Man', |
|||
'Israel', |
|||
'Italy', |
|||
'Jamaica', |
|||
'Japan', |
|||
'Jersey', |
|||
'Jordan', |
|||
'Kazakhstan', |
|||
'Kenya', |
|||
'Kiribati', |
|||
'Korea, Democratic People\'s Republic of', |
|||
'Korea, Republic of', |
|||
'Kuwait', |
|||
'Kyrgyzstan', |
|||
'Lao People\'s Democratic Republic', |
|||
'Latvia', |
|||
'Lebanon', |
|||
'Lesotho', |
|||
'Liberia', |
|||
'Libya', |
|||
'Liechtenstein', |
|||
'Lithuania', |
|||
'Luxembourg', |
|||
'Macao', |
|||
'Macedonia, Republic Of', |
|||
'Madagascar', |
|||
'Malawi', |
|||
'Malaysia', |
|||
'Maldives', |
|||
'Mali', |
|||
'Malta', |
|||
'Marshall Islands', |
|||
'Martinique', |
|||
'Mauritania', |
|||
'Mauritius', |
|||
'Mayotte', |
|||
'Mexico', |
|||
'Micronesia, Federated States of', |
|||
'Moldova, Republic of', |
|||
'Monaco', |
|||
'Mongolia', |
|||
'Montenegro', |
|||
'Montserrat', |
|||
'Morocco', |
|||
'Mozambique', |
|||
'Myanmar', |
|||
'Namibia', |
|||
'Nauru', |
|||
'Nepal', |
|||
'Netherlands', |
|||
'New Caledonia', |
|||
'New Zealand', |
|||
'Nicaragua', |
|||
'Niger', |
|||
'Nigeria', |
|||
'Niue', |
|||
'Norfolk Island', |
|||
'Northern Mariana Islands', |
|||
'Norway', |
|||
'Oman', |
|||
'Pakistan', |
|||
'Palau', |
|||
'Palestinian Territory, Occupied', |
|||
'Panama', |
|||
'Papua New Guinea', |
|||
'Paraguay', |
|||
'Peru', |
|||
'Philippines', |
|||
'Pitcairn', |
|||
'Poland', |
|||
'Portugal', |
|||
'Puerto Rico', |
|||
'Qatar', |
|||
'Reunion', |
|||
'Romania', |
|||
'Russian Federation', |
|||
'Rwanda', |
|||
'Saint Barthélemy', |
|||
'Saint Helena, Ascension and Tristan da Cunha', |
|||
'Saint Kitts and Nevis', |
|||
'Saint Lucia', |
|||
'Saint Martin (French Part)', |
|||
'Saint Pierre and Miquelon', |
|||
'Saint Vincent and the Grenadines', |
|||
'Samoa', |
|||
'San Marino', |
|||
'Sao Tome and Principe', |
|||
'Saudi Arabia', |
|||
'Senegal', |
|||
'Serbia', |
|||
'Seychelles', |
|||
'Sierra Leone', |
|||
'Singapore', |
|||
'Sint Maarten (Dutch Part)', |
|||
'Slovakia', |
|||
'Slovenia', |
|||
'Solomon Islands', |
|||
'Somalia', |
|||
'South Africa', |
|||
'South Georgia and the South Sandwich Islands', |
|||
'South Sudan', |
|||
'Spain', |
|||
'Sri Lanka', |
|||
'Sudan', |
|||
'Suriname', |
|||
'Svalbard and Jan Mayen', |
|||
'Swaziland', |
|||
'Sweden', |
|||
'Switzerland', |
|||
'Syrian Arab Republic', |
|||
'Taiwan', |
|||
'Tajikistan', |
|||
'Tanzania, United Republic of', |
|||
'Thailand', |
|||
'Timor-Leste', |
|||
'Togo', |
|||
'Tokelau', |
|||
'Tonga', |
|||
'Trinidad and Tobago', |
|||
'Tunisia', |
|||
'Turkey', |
|||
'Turkmenistan', |
|||
'Turks and Caicos Islands', |
|||
'Tuvalu', |
|||
'Uganda', |
|||
'Ukraine', |
|||
'United Arab Emirates', |
|||
'United Kingdom', |
|||
'United States', |
|||
'United States Minor Outlying Islands', |
|||
'Uruguay', |
|||
'Uzbekistan', |
|||
'Vanuatu', |
|||
'Venezuela', |
|||
'Viet Nam', |
|||
'Virgin Islands, British', |
|||
'Virgin Islands, U.S.', |
|||
'Wallis and Futuna', |
|||
'Western Sahara', |
|||
'Yemen', |
|||
'Zambia', |
|||
'Zimbabwe' |
|||
]; |
|||
|
|||
/* eslint-disable */ |
|||
export const POSTAL_CODE_PATTERNS = { |
|||
'United States': '(\\d{5}([\\-]\\d{4})?)', |
|||
'Australia': '[0-9]{4}', |
|||
'Austria': '[0-9]{4}', |
|||
'Belgium': '[0-9]{4}', |
|||
'Brazil': '[0-9]{5}[\\-]?[0-9]{3}', |
|||
'Canada': '^(?!.*[DFIOQU])[A-VXY][0-9][A-Z][ -]?[0-9][A-Z][0-9]$', |
|||
'Denmark': '[0-9]{3,4}', |
|||
'Faroe Islands': '[0-9]{3,4}', |
|||
'Netherlands': '[1-9][0-9]{3}\\s?[a-zA-Z]{2}', |
|||
'Germany': '[0-9]{5}', |
|||
'Hungary': '[0-9]{4}', |
|||
'Italy': '[0-9]{5}', |
|||
'Japan': '\\d{3}-\\d{4}', |
|||
'Luxembourg': '(L\\s*(-|—|–))\\s*?[\\d]{4}', |
|||
'Poland': '[0-9]{2}\\-[0-9]{3}', |
|||
'Spain': '((0[1-9]|5[0-2])|[1-4][0-9])[0-9]{3}', |
|||
'Sweden': '\\d{3}\\s?\\d{2}', |
|||
'United Kingdom': '[A-Za-z]{1,2}[0-9Rr][0-9A-Za-z]? [0-9][ABD-HJLNP-UW-Zabd-hjlnp-uw-z]{2}' |
|||
}; |
|||
/* eslint-enable */ |
|||
|
|||
@ -0,0 +1,58 @@ |
|||
<!-- |
|||
|
|||
Copyright © 2016-2024 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. |
|||
|
|||
--> |
|||
<mat-form-field [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}"> |
|||
<mat-label>{{ labelText }}</mat-label> |
|||
<input matInput type="text" |
|||
#countryInput |
|||
formControlName="country" |
|||
(focusin)="onFocus()" |
|||
(blur)="onTouched()" |
|||
[required]="required" |
|||
[matAutocomplete]="countryAutocomplete"> |
|||
<button *ngIf="countryFormGroup.get('country').value && !disabled" |
|||
type="button" |
|||
matSuffix mat-icon-button aria-label="Clear" |
|||
(click)="clear()"> |
|||
<mat-icon class="material-icons">close</mat-icon> |
|||
</button> |
|||
<mat-autocomplete class="tb-autocomplete" |
|||
#countryAutocomplete="matAutocomplete" |
|||
[displayWith]="displayCountryFn"> |
|||
<mat-option *ngFor="let country of filteredCountries | async" [value]="country"> |
|||
<span style="font-size: 20px;padding-right: 4px;">{{country.flag}}</span> |
|||
<span [innerHTML]="country.name | highlight:searchText"></span> |
|||
</mat-option> |
|||
<mat-option *ngIf="!(filteredCountries | async)?.length" [value]="null" class="tb-not-found"> |
|||
<div class="tb-not-found-content" (click)="$event.stopPropagation()"> |
|||
<div *ngIf="!textIsNotEmpty(searchText); else searchNotEmpty"> |
|||
<span translate>contact.no-country-found</span> |
|||
</div> |
|||
<ng-template #searchNotEmpty> |
|||
<span> |
|||
{{ 'contact.no-country-matching' | translate: |
|||
{country: (searchText | truncate: true: 6: '...')} }} |
|||
</span> |
|||
</ng-template> |
|||
</div> |
|||
</mat-option> |
|||
</mat-autocomplete> |
|||
<mat-hint *ngIf="autocompleteHint">{{ autocompleteHint }}</mat-hint> |
|||
<mat-error *ngIf="countryFormGroup.get('country').hasError('required')"> |
|||
{{ requiredText }} |
|||
</mat-error> |
|||
</mat-form-field> |
|||
@ -0,0 +1,209 @@ |
|||
///
|
|||
/// Copyright © 2016-2024 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, ElementRef, EventEmitter, forwardRef, Input, OnInit, Output, ViewChild } from '@angular/core'; |
|||
import { Country, CountryData } from '@shared/models/country.models'; |
|||
import { |
|||
ControlValueAccessor, |
|||
FormBuilder, |
|||
FormGroup, |
|||
NG_VALIDATORS, |
|||
NG_VALUE_ACCESSOR, |
|||
ValidationErrors, |
|||
Validator |
|||
} from '@angular/forms'; |
|||
import { isNotEmptyStr } from '@core/utils'; |
|||
import { Observable, of } from 'rxjs'; |
|||
import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators'; |
|||
import { SubscriptSizing } from '@angular/material/form-field'; |
|||
import { coerceBoolean } from '@shared/decorators/coercion'; |
|||
import { TranslateService } from '@ngx-translate/core'; |
|||
|
|||
interface CountrySearchData extends Country { |
|||
searchText?: string; |
|||
} |
|||
|
|||
@Component({ |
|||
selector: 'tb-country-autocomplete', |
|||
templateUrl: 'country-autocomplete.component.html', |
|||
providers: [ |
|||
CountryData, |
|||
{ |
|||
provide: NG_VALUE_ACCESSOR, |
|||
useExisting: forwardRef(() => CountryAutocompleteComponent), |
|||
multi: true |
|||
}, |
|||
{ |
|||
provide: NG_VALIDATORS, |
|||
useExisting: forwardRef(() => CountryAutocompleteComponent), |
|||
multi: true |
|||
} |
|||
] |
|||
}) |
|||
export class CountryAutocompleteComponent implements OnInit, ControlValueAccessor, Validator { |
|||
|
|||
@Input() |
|||
labelText = this.translate.instant('contact.country'); |
|||
|
|||
@Input() |
|||
requiredText = this.translate.instant('contact.country-required'); |
|||
|
|||
@Input() |
|||
autocompleteHint: string; |
|||
|
|||
@Input() |
|||
disabled: boolean; |
|||
|
|||
@Input() |
|||
@coerceBoolean() |
|||
required = false; |
|||
|
|||
@Input() |
|||
subscriptSizing: SubscriptSizing = 'fixed'; |
|||
|
|||
@ViewChild('countryInput', {static: true}) countryInput: ElementRef; |
|||
|
|||
@Output() |
|||
selectCountryCode = new EventEmitter<string>(); |
|||
|
|||
countryFormGroup: FormGroup; |
|||
|
|||
searchText = ''; |
|||
|
|||
filteredCountries: Observable<Array<Country>>; |
|||
|
|||
onTouched = () => { |
|||
}; |
|||
private propagateChange: (value: any) => void = () => { |
|||
}; |
|||
|
|||
private modelValue: Country; |
|||
|
|||
private allCountries: CountrySearchData[] = this.countryData.allCountries; |
|||
private initSearchData = false; |
|||
private dirty = false; |
|||
|
|||
constructor(private fb: FormBuilder, |
|||
private countryData: CountryData, |
|||
private translate: TranslateService) { |
|||
this.countryFormGroup = this.fb.group({ |
|||
country: [''] |
|||
}); |
|||
} |
|||
|
|||
ngOnInit(): void { |
|||
this.filteredCountries = this.countryFormGroup.get('country').valueChanges.pipe( |
|||
debounceTime(150), |
|||
tap(value => { |
|||
let modelValue: Country; |
|||
if (typeof value === 'string' || !value) { |
|||
modelValue = null; |
|||
} else { |
|||
modelValue = value; |
|||
} |
|||
this.updateView(modelValue); |
|||
if (value === null) { |
|||
this.clear(); |
|||
} |
|||
}), |
|||
map(value => value ? (typeof value === 'string' ? value : value.name) : ''), |
|||
distinctUntilChanged(), |
|||
switchMap(name => of(this.fetchCountries(name))), |
|||
share() |
|||
); |
|||
} |
|||
|
|||
registerOnChange(fn: any): void { |
|||
this.propagateChange = fn; |
|||
} |
|||
|
|||
registerOnTouched(fn: any): void { |
|||
this.onTouched = fn; |
|||
} |
|||
|
|||
setDisabledState(isDisabled: boolean): void { |
|||
this.disabled = isDisabled; |
|||
if (isDisabled) { |
|||
this.countryFormGroup.disable({emitEvent: false}); |
|||
} else { |
|||
this.countryFormGroup.enable({emitEvent: false}); |
|||
} |
|||
} |
|||
|
|||
validate(): ValidationErrors | null { |
|||
return this.countryFormGroup.valid ? null : { |
|||
countryFormGroup: false |
|||
}; |
|||
} |
|||
|
|||
writeValue(country: string) { |
|||
this.dirty = true; |
|||
|
|||
const findCountry = isNotEmptyStr(country) ? this.allCountries.find(item => item.name === country) : null; |
|||
|
|||
this.modelValue = findCountry || null; |
|||
this.countryFormGroup.get('country').patchValue(this.modelValue || '', { emitEvent: false }); |
|||
this.selectCountryCode.emit(this.modelValue ? this.modelValue.iso2 : null); |
|||
} |
|||
|
|||
displayCountryFn(country?: Country): string | undefined { |
|||
return country ? `${country.flag} ${country.name}` : undefined; |
|||
} |
|||
|
|||
onFocus() { |
|||
if (this.dirty) { |
|||
this.countryFormGroup.get('country').updateValueAndValidity({onlySelf: true}); |
|||
this.dirty = false; |
|||
} |
|||
} |
|||
|
|||
textIsNotEmpty(text: string): boolean { |
|||
return (text && text.length > 0); |
|||
} |
|||
|
|||
clear() { |
|||
this.countryFormGroup.get('country').patchValue('', {emitEvent: true}); |
|||
setTimeout(() => { |
|||
this.countryInput.nativeElement.blur(); |
|||
this.countryInput.nativeElement.focus(); |
|||
}, 0); |
|||
} |
|||
|
|||
private fetchCountries(searchText: string): Country[] { |
|||
this.searchText = searchText; |
|||
if (!this.initSearchData) { |
|||
this.allCountries.forEach(country => { |
|||
country.searchText = `${country.name} ${country.iso2}`.toLowerCase(); |
|||
}); |
|||
this.initSearchData = true; |
|||
} |
|||
if (isNotEmptyStr(searchText)) { |
|||
const filterValue = searchText.toLowerCase(); |
|||
return this.allCountries.filter(country => country.searchText.includes(filterValue)); |
|||
} |
|||
return this.allCountries; |
|||
} |
|||
|
|||
private updateView(value: Country | null) { |
|||
if (this.modelValue?.name !== value?.name) { |
|||
this.modelValue = value; |
|||
this.propagateChange(this.modelValue); |
|||
if (value) { |
|||
this.selectCountryCode.emit(value.iso2); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue